六问六答理解ForkJoin原理

ForkJoin线程池的基本原理是将任务划分为子任务,直到它们小到足以并行执行。
本文通过回答六个问题,让您深入了解ForkJoin的运行机制。
首先,ForkJoin线程池的工作原理与合并计算类似。
通过工作秘密,线程获得并行执行的小任务。
当我们使用ForkJoin并发执行任务时,我们需要继承RecursiveTask类作为ForkJoin包装器的调度对象。
其内部实现包括fork和join操作,以及确保任务并行执行和结果合并的状态同步机制。
当任务分叉时,它实际上将任务异步添加到当前线程的队列中。
Fork任务首先分配给自己处理,当自己无法完成时,可能会被其他线程获取。
join操作用于获取子任务的结果,并通过循环判断子任务的状态。
如果未完成,则继续执行其他任务,直至完成。
ForkJoin线程池中,任务存储和并发处理是通过队列实现的,保证任务的有序执行和状态同步。
当多个线程尝试同时向队列末尾插入任务时,队列的并发属性可确保任务得到正确处理。
ForkJoin线程池广泛应用于并行计算场景,比如Java8Stream的并行功能,通过ForkJoin分解任务并并行执行。
开发者可以根据需要自定义线程池大小。
通过深入理解ForkJoin的原理和实现,我们可以充分利用并行计算的优势,提高任务执行的效率,实现高性能计算。

java线程池机制的原理是什么?

线程池属于对象池所有的对象池都有一个非常重要的共同点,就是最大程度地复用对象。
然后就是线程池最重要的特性。
首先,创建线程需要额外的(相比于执行任务所需资源的开销)。
操作系统创建线程时,至少要创建以下资源::(1)线程核心对象:用于管理线程context(2)用户态执行栈这些资源被线程占用后,操作系统和用户都无法逆向使用它们,销毁线程需要回收资源和一定的开销线程多导致切换过多性能无法估量系统完成线程切换必须经过以下过程:(1)从用户态切换到内核态(2)将CPU寄存器的值保存到内核对象(3)打开自动自旋锁,根据调度策略确定下一个要运行的线程,如果要运行的线程不是同一个进程中的线程则释放自旋锁进程也会切换进程环境为虚拟(4)将内核对象的值写入新的CPU寄存器因此,线程池的目的是减少创建和切换线程的额外开销,利用现有线程多次执行多个任务来提高性能系统的处理能力。

阻塞队列和线程池原理

在讲阻塞队列之前,我们首先要了解什么是队列?队列是一种特殊的线性列表。
将队列项插入队列称为排队,从队列中删除队列项称为出队。
由于队列只允许一端插入,另一端删除,只有先进入队列的元素才能先从队列中删除,所以队列也称为先进先出(FIFO-firstinfirstout)线性队列。
什么是阻塞队列(1)支持插入阻塞方式:即当队列已满时,队列会阻塞线程的插入元素,从而使队列未满。
(2)支持非阻塞方式:这意味着当队列为空时,获取元素的线程将一直等待,直到队列不再为空。
在线程的世界中,生产者是产生数据的线程,消费者是消费数据的线程。
在多线程开发中,如果生产者的处理速度太快而消费者的处理速度太慢,那么生产者必须等待消费者处理完成后才能继续生产数据。
同理,如果消费者的处理能力大于生产者,消费者就必须等待生产者。
为了解决生产能力和消费能力不平等的问题,生产者-消费者模式应运而生。
生产者-消费者模式通过容器解决了生产者和消费者之间的强耦合问题。
生产者和消费者之间并不直接通信,他们通过阻塞队列进行通信,因此生产者生产数据后,不需要等待消费者处理,而是直接扔到阻塞队列中,它不会询问。
阻塞队列相当于一个缓冲区,平衡生产者和消费者的处理能力。
阻塞队列常用于生产者和消费者场景。
生产者是将项目添加到队列的线程,而消费者是从队列中取出项目的线程。
阻塞队列是生产者用来存储项目和消费者用来获取项目的容器。
(3)抛出异常:当队列已满时,如果向队列中插入另一个项目,则会抛出IllegalStateException(“Queuefull”)异常。
当你是队列为空,从队列中检索元素将抛出NoSuchElementException。
返回一个特殊值:当向队列中插入一个项目时,会返回该项目是否插入成功,如果成功则返回true。
如果是remove方法,则会从队列中取出一个元素,如果没有元素,则返回null。
·始终阻塞:当阻塞队列已满时,如果生产者线程将项目放入队列中,则队列将阻塞生产者线程,直到队列变得可用或响应中断而退出。
当队列为空时,如果消费者线程从队列中取出元素,队列会阻塞消费者线程,直到队列为空。
·超时退出:当阻塞队列已满时,如果生产者线程向队列中插入元素,队列会阻塞生产者线程一段时间,如果超过指定的时间,生产者线程就会退出。
常用的阻塞队列·ArrayBlockingQueue:由数组结构组成的特定阻塞队列。
·LinkedBlockingQueue:由链表结构组成的特定阻塞队列。
·PriorityBlockingQueue:无限制的阻塞队列,支持优先级排序。
·DelayQueue:使用优先级队列实现的无限阻塞队列。
·SynchronousQueue:不存储项目的阻塞队列。
·LinkedTransferQueue:由链表结构组成的无限队列。
LinkedBlockingDeque:由链表结构组成的双向阻塞队列。
上面所有的阻塞队列都实现了BlockingQueue接口,并且都是线程安全的。
所谓有限队列,是指阻塞队列的长度有优先级,满了则阻塞,不允许排队。
无限队列意味着进入的元素不受限制,但是一旦超过系统警告值,JVM会直接帮你杀掉。
同时,非常重要的是要记住,无限队列也会被阻塞,因为在取物品时,如果队列为空,消费者也会阻塞相应的取。
ArrayBlockingQueue:数组实现的队列默认不保证公平访问。
公平访问原则是指线程先被阻塞,先到达队列。
这对于连锁店来说是不公平的首先等待当队列可用时,被阻塞的线程可能会竞争那些有资格访问队列的线程,并且可能会被阻塞的线程最后到达队列。
初始化时可以设置一些参数,这是一个使用链表实现的有限阻塞队列。
该队列的默认最大长度为Integer.MAX_VALUE。
该队列按照先进先出的原则对项目进行排序。
PriorityBlockingQueue是一个支持优先级的无限阻塞队列。
默认情况下,项目按自然升序排序。
您还可以自定义类来实现CompareTo()方法来指定项目排序规则,或者在初始化PriorityBlockingQueue时指定Comparator构造函数参数来对项目进行排序。
需要注意的是,不保证具有相同优先级的项目的排名。
它是一个无限的阻塞队列,支持获取迟到的项目。
队列是使用PriorityQueue实现的。
队列中的元素必须实现延迟接口。
创建项目时,您可以指定从队列中获取当前项目所需的时间。
仅当延迟期到期时才能从队列中获取项目。
它是一个不存储项目的阻塞队列。
每个put操作都必须等待take操作,否则无法添加项目。
SynchronousQueue可以认为是一个passthrough,负责将生产者线程处理的数据直接传递给消费者线程。
队列本身不存储任何项目,这使得它非常适合传递场景。
SynchronousQueue的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue。
尝试移动的方式有多种(1)在移动方法中,如果当前有消费者等待接收物品(当消费者使用有时间限制的take()方法或poll()方法时),则移动将会完成。
该方法可以从生产者传入项目,并且该项目立即传输给消费者。
如果没有消费者等待接收该物品,则transfer方法会将物品存储在队列的后部节点中,等待消费者消费完该物品后再返回。
(2)TryTransfer方法TryTransfer方法用于测试生产者传递的物品是否可以直接传输给消费者。
如果没有消费者等待接收商品,则会返回错误。
TryTransfer方法与Transfer方法的区别在于,TryTransfer方法无论消费者是否收到都立即返回,而TryTransfer方法必须等待。
运输直至消费者消费完后再返回。
LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列。
所谓双向队列,是指队列的两端都可以插入和移除项目。
由于双向队列包含进程队列中的另一个条目,因此当多个线程同时加入队列时,竞争减少了一半。
还有更多方法,如addFirst、addLast、offerFirst、offerLast、peekFirst、peekLast以First结尾的方法表示插入或获取双端队列中的第一项(peek)。
以last结尾的方法指示插入、检索或删除双端队列中的最后一个元素。
另外,add方法相当于addLast,remove方法相当于RemoveFirst。
但是take方法相当于takeFirst,不知道使用带有first和last后缀的方法是不是JDK的bug更明显。
LinkedBlockingDeque初始化时可以调整容量,防止过度膨胀。
此外,双向队列可以用于“工作窃取”模式。
Java中的线程池是一个包含大部分应用场景的并发框架。
所有需要异步或同步执行任务的程序都可以使用线程池。
在开发过程中,合理使用线程池可以带来三个好处。
第一:减少资源消耗。
通过重用已创建的线程来降低线程创建和销毁的成本。
第二:提高响应速度。
当任务到达时,可以立即执行,无需等待线程创建。
假设服务器完成任务所需的时间为:T1创建线程时间,T2在线程中执行任务,T3销毁线程时间。
如果:T1+T3远大于T2,则可以使用线程池来提高服务器性能。
线程池是一项专注于如何缩短或调整T1和T3时间,从而提高服务器软件性能的技术。
它将T1和T3分别安排在服务器软件的开始和结束时段或者某些空闲时间段,这样服务器软件在处理客户端请求时,不会给T1和T3带来额外的负载。
第三:提高驾驭话题的能力。
线程是稀缺资源,如果无限创建,不仅会消耗系统资源,还会降低系统稳定性并使用线程池进行分配和调优。
并进行统一监控。
ThreadPoolExecutor的类关系Executor是一个接口,是Executor框架的基础,它将任务提交和执行分开。
ExecutorService接口继承了Executor并对shutdown()和dispatch()做了一些扩展。
一个实现类,它池化用于执行提交的任务的线程。
ScheddedExecutorService接口继承了ExecutorService接口,为E​​xecutorService提供了“周期性执行”的功能;ScheduledThreadPoolExecutor是一个实现类,可以在一定延迟后运行命令,或者定时执行命令。
ScheduledThreadPoolExecutor比Timer更灵活、更强大。
创建线程池中各个参数的含义publicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerhandler)线程池中核心线程的数量当任务提交时,线程池创建一个新线程来执行任务,直到该数字变为当前数字forthreads等于corePoolSize,如果当前线程数为corePoolSize;仍在发送的内容保留在阻塞队列中等待执行;如果执行线程池的prestartAllCoreThreads()方法,线程池将创建并预启动所有核心线程。
线程池中允许的最大线程数。
如果当前阻塞队列已满,还有任务继续提交,则会创建一个新的线程来执行该任务,前提是当前线程数小于线程空闲时PoolSize的最大生存时间,即当您不存在要执行的任务时线程继续生存的时间。
默认情况下,该参数仅在线程数大于corePoolSize时有用。
WorkQueuekeepAliveTime的时间单位必须是阻塞队列BlockingQueue。
当线程池中的线程数量超过其CorePoolSize时,线程就会进入阻塞队列进行阻塞等待。
线程池通过工作队列实现阻塞功能。
创建线程工厂通过自定义线程工厂,你可以为每个新创建的线程分配一个可识别的线程名称,当然你也可以更自由地进行更多的线程设置,比如将所有线程设置为守护线程。
线程池饱和策略。
当阻塞队列已满并且没有空闲工作线程时,如果继续提交任务,应该采取策略为了处理任务,线程池提供了4种策略:AbortPolicy、CallerRunsPolicy。
DiscardOldestPolicy和DiscardPolicy通俗解释如下AbortPolicy:直接抛出异常(开始骂人)。
CallerRunsPolicy:使用调用者所在的线程来执行任务(既然你要让我干活,我没时间做这个,你自己做吧)DiscardOldestPolicy:丢弃最旧的任务,丢弃最旧的任务第一次进入队列的任务,并执行当前任务(忽略旧爱,拥抱新爱)忽略策略:直接忽略该任务(直男拒绝新爱)线程池工作机制1.当前线程小于基本线程进程数(corePoolSize),直接创建线程2.如果当前线程大于核心线程,则直接将下一个任务添加到队列中3.如果队列已满并且有新任务且最大PoolSize大于0,限制可以创建的最大线程数减去PoolSize的最大大小corePoolSize4。
如果当前线程数达到最大PoolSize,当任务再次到来时该策略将被拒绝。
开始并提交作业。
execute()方法执行后不返回执行结果,无法判断线程池是否执行完任务,以后根据get(.)获取是否已执行。
不能使用execute()方法,该方法会阻塞当前线程一段时间并立即返回,并且任务可能无法完成。
通过调用线程池的ShutdownNow方法关闭线程池。
它们的原理是遍历工作线程进入线程池,然后一一调用线程中断方法来中断线程,因此无法响应中断的任务可能永远不会被终止。
但它们之间也存在一些差异。
ShutdownNow首先将线程池状态更改为STOP,然后尝试暂停或挂起所有正在执行任务的线程,并返回等待执行的任务列表,而Shutdown只是将线程池状态设置为SHUTDOWN状态,然后中断所有线程现有的任务没有被执行。
只要调用这两个关闭方法中的任何一个,isShutdown方法就会返回true。
当所有任务都关闭时,就意味着线程池已经关闭粘合成功。
这时,调用isTerminaed方法就会返回正确的值。
至于应该调用什么方法来关闭线程池,应该根据提交到线程池的任务的属性来确定,如果任务不需要关闭,通常会调用ShutdownNow方法。
完成后,可以调用ShutdownNow方法。
正确配置线程池。
如果想要正确配置线程池,首先要分析任务的属性。
应将尽可能小的线程配置为IO密集型、CPU密集型任务,例如为单个线程配置Ncpu+线程池。
即使计算密集型线程有时由于页面丢失错误或其他原因而停止,这个“额外”线程也确保不会浪费CPU样式的周期。
页面错误(Pagefault,也称为硬错误、硬中断、分页错误、缺页、缺页中断、缺页错误等)是指程序尝试访问映射在虚拟地址空间中的内存时,但目前还没有available(当页面加载到物理内存时,CPU的内存管理单元发出的中断)由于I/O密集型任务线程并不总是执行任务,因此必须配置尽可能多的线程,例如2.*必须是以尽可能多的顺序配置任务进程因为IO操作不占用CPU不要让CPU空闲增加混合任务的线程数,如果可以划分为CPU密集型任务,只要两个任务之间的执行时间相差不大即可。
太大,分解执行的吞吐量会高于顺序执行的吞吐量,如果两个任务的执行时间相差太大,则无需分析它们当前机器的CPU数量。
通过该方法得到的Runtime.getRuntime().availableProcessors()。