在代码开发中使用线程池,主要能带来三个好处:
注:当线程请求的栈容量超过栈允许的最大容量的话,Java虚拟机将会抛出一个StackOverFlow;如果java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成扩展,或者在新建立的线程的时候没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将会抛出一个OutOfMemory。
图1 线程池的主要处理流程
当提交一个新任务到线程池时,线程池的处理流程如下:
图2 ThreadPoolExecutor执行示意图
ThreadPoolExecutor执行executor的方法分以下4种情况:
在执行execute()方法时,尽可能避免获取全局锁。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的executor方法调用都执行步骤2,而步骤2是不需要获取全局锁。
只有一个接口方法void execute(Runnable command)
线程池的运行状态值和当前线程数量都存在了一个int类型数字(ctl)里,高三位存的是状态,其他位用于记录数量。而且,状态是只能按照上面从上到下的顺序变化的。
主要分三步:
1、如果运行线程数小于coorPoolSize,用给定的Runnable对象新开一个线程去执行,并且执行addWorker方法会以原子性操作去检查runState和workerCount,以防止当返回false的时候添加了不该添加的线程;
2、如果任务能够添加到队列当中,仍需要对添加线程进行双重检查,有可能添加的线程在前一次检查时,已经死亡,又或者进入该方法时候,线程池关闭了。所以需要复查状态,并在有必要的情况下进行停止时回滚操作,或者在没有线程的时候开启一个线程;
3、如果任务无法入列,那么我们需要尝试新增一个线程,如果新建线程失败了,我们就知道线程池关闭了,或者饱和了,就要拒绝这个任务。
具体源码如下:
execute执行流程图
其中,addWorker主要创建新线程并执行任务,该方法源码:
主要流程:
1、获取线程池的控制状态。进行判断,不符合则返回false,符合进入下一步;
2、死循环,判断workCount是否大于上限,或者大于corePoolSize/maximPoolSize,没有的话则对workCount+1操作;
3、如果不符合上述判断条件或者+1操作失败,则再次获取线程池的控制状态,获取runState与刚开始获取的runState相比,如果不一致跳出内层循环绩效外层循环,否则绩效内层循环;
4、+1操作成功后,使用重入锁ReentrantLock来保证workers当中添加worker实例,添加成功后就启动该实例。
如果addWorker方法添加worker失败,就会调用addWorkerFailed方法,将任务从workers中移除,并且workerCount做-1操作:
当线程执行了非正常逻辑操作时,都会需要执行tryTerminate方法:
在线程中线程被封装成Worker对象形式存在:
这里线程的runnable是自身,run方法中执行的是runWorker方法,启动线程,执行run方法,最终调用的是runWorker方法。
而runWorker方法是属于ThreadPoolExecutor方法:
代码逻辑:
1、首先,执行w.unlock(),将AQS的状态改为0,因为只有getState大于等于0,线程才可以被中断;
2、判断firstTask是否为null,为null则通过getTask()方法获取任务,不为空则往下执行;
3、判断是否满足中断状态,符合的话设置中断标记;
4、执行beforeExecute,task.run,afterExecute方法;
5、任何一个出异常都会导致任务执行终止,进入processWorkerExit方法来退出任务;
6、正常执行会回到步骤2;
其中,当firstTask为null的时候,会调用getTask方法接着获取任务去执行:
allowCoreThreadTimeOut默认为false,即使线程池空闲也不会被销毁;倘若为true,在keepAliveTime内,空闲仍会被销毁。如果线程允许空闲等待而不被销毁timed==false,wokerQueue.take任务:如果阻塞队列为空,当前线程会被挂起等待;当队列中有任务加入时,线程被唤醒,take方法返回任务,并执行;
如果线程不允许无休止空闲timed==true,workQueue.poll任务:如果在阻塞队列中没有任务,则返回null;
代码逻辑:
1、获取线程池的控制状态和runState状态,判断线程池是否已经关闭或者正在关闭,是的话则workerCount-1,操作返回null;
2、获取workerCount判断是否大于核心线程池数;
3、判断workerCount是否大于最大线程池数目或者已经超时,是的话workCount-1操作,-1成功返回null,不成功则回到步骤1继续;
4、判断workerCount是否大于核心线程池,大于则用poll方法获取,否则用take方法获取任务;
其中,rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty()):
大于等于shutdown状态只有:shutdown,stop,tidying,terminated,大于等于stop状态只有:stop,tidying,terminated,所以只有处于shutdown状态的时候,才会去判断workerQueue是否为空,其他状态(stop,tidying,terminated)的话,不会去判断任务队列是否为空,只有当状态为shutdown并且队列为空的时候,才会退出。
在执行任务过程中,任务执行完或者出现异常中断执行的时候,会调用processWorkerExit方法:
代码逻辑:
shutdown方法改变状态为shutdown,并在尝试给每个线程设置中断标志,接着结合getTask方法返回null来停止,移除线程,最后尝试终止线程。
图2 Executor框架的类与接口
图 2 Executor框架使用示意图
1)CachedThreadPool
CachedThreadPool是通过Executors静态方法创建的ThreadPoolExcutor实例,是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。该线程池在执行时,大量短生命周期的异步任务,可以提高程序性能。调用execute时,可以重用之前已构造的可用线程,如果不存在可用线程,则会重新重新一个新的线程并加入到线程池中。如果线程超过60s还未被使用,就会被中止并从缓存中移除。因此,线程池在长时间空闲后不会消耗任务资源。
CachedThreadPool的线程数是无限增大,构造函数中:
因此,如果新任务来的时候,CachedThreadPool就创建新的线程(极端情况下,会因为创建过多线程而耗尽CPU和内存资源),如果有空闲线程就会重复使用,线程空闲60s就会被回收。
CacheThreadPool工作流程:
2)FixedThreadPool
FixedThreadPool是通过Executors静态方法创建的ThreadPoolExcutor实例。这个实例会复用固定数量的线程处理一个共享无边界队列。任何时间点,最多有nThreads个线程胡处于活动状态执行任务。如果当所有线程都是在活动时,有多的任务被提交过来,那么会一直在队列中等待知道线程可用。如果任何线程在执行过程中因为错误而终止,新的线程会替代它原来的位置执行后续的任务。所有线程都会一致存于线程池中,直到显示执行ExecutorService.shutdown方法关闭。
FixedThreadPool的corePoolSize和maximumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads;keepAliveTime设置为0L,多余空闲线程会被立即终止。
队列采用的是无边界LinkedBlockingQueue作为线程池的工作队列,其中,队列容量为Integer.MAX_VALUE:
3)SingleThreadPool
SingleThreadPool是通过Executors静态方法创建的ThreadPoolExcutor实例。该线程池只会使用单个线程来执行一个无边界队列(如果单个线程在执行过程中因为某些错误中止,新的线程会替代他执行后续任务)。它可以保证认为是按顺序执行的,任何时候都不会有多于一个的线程处于活跃状态。
SingleThreadPool的corePoolSize和maximumPoolSize被设置为1,其他参数与FixedThreadPool相同;
队列同样采用的是无界队列LinkedBlockingQueue作为线程池的工作队列,队列容量大小Integer.MAX_VALUE:
运行结果如下:
thread-1
thread-4
thread-2
thread-3
thread-5