Java线程池笔记

Java线程池

在代码开发中使用线程池,主要能带来三个好处:

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建,能够立即运行;
  3. 提高线程的可管理性。线程是稀缺资源,如果无限创建线程,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

注:当线程请求的栈容量超过栈允许的最大容量的话,Java虚拟机将会抛出一个StackOverFlow;如果java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成扩展,或者在新建立的线程的时候没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将会抛出一个OutOfMemory。

 

  • 线程池实现原理:

 

图1 线程池的主要处理流程

 

 

当提交一个新任务到线程池时,线程池的处理流程如下:

  1. 线程池判断核心线程里的线程是否都在执行任务。如果不是,则会创建一个新的线程来执行任务;如果核心线程池里的线程都在执行执行任务,则进入下一个流程;
  2. 线程池判断工作队列是否已经满了。如果工作队列没有满,则将新提交的任务存储在这个工作队列;如果这个工作队列满了,则进入下一个流程;
  3. 线程池判断线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果满了。则交给饱和和策略来处理这个任务。

 

 

 

 

图2 ThreadPoolExecutor执行示意图

 

ThreadPoolExecutor执行executor的方法分以下4种情况:

  1. 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步需要获取全局锁);
  2. 如果运行线程等于或者多于corePoolSize,则将任务加入BlockingQueue;
  3. 如果无法将任务加入BlcokingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步需要获取全局锁);
  4. 如果创建新的线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

在执行execute()方法时,尽可能避免获取全局锁。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的executor方法调用都执行步骤2,而步骤2是不需要获取全局锁。

1. ThreadPoolExecutor的源码实现:

  1. 最上层接口Executor:

只有一个接口方法void execute(Runnable command)

  1. ThreadPoolExecutor主要成员变量:

线程池的运行状态值和当前线程数量都存在了一个int类型数字(ctl)里,高三位存的是状态,其他位用于记录数量。而且,状态是只能按照上面从上到下的顺序变化的。

  1. 线程池执行流程,核心方法void execute(Runnable command):

主要分三步:

1、如果运行线程数小于coorPoolSize,用给定的Runnable对象新开一个线程去执行,并且执行addWorker方法会以原子性操作去检查runState和workerCount,以防止当返回false的时候添加了不该添加的线程;

2、如果任务能够添加到队列当中,仍需要对添加线程进行双重检查,有可能添加的线程在前一次检查时,已经死亡,又或者进入该方法时候,线程池关闭了。所以需要复查状态,并在有必要的情况下进行停止时回滚操作,或者在没有线程的时候开启一个线程;

3、如果任务无法入列,那么我们需要尝试新增一个线程,如果新建线程失败了,我们就知道线程池关闭了,或者饱和了,就要拒绝这个任务。

具体源码如下:

2.execute方法执行流程图如下:

 

 

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方法获取任务;

  1. 判断任务是否为空,不为空则返回获取的任务,否则回到步骤1继续;

其中,rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty()):

大于等于shutdown状态只有:shutdown,stop,tidying,terminated,大于等于stop状态只有:stop,tidying,terminated,所以只有处于shutdown状态的时候,才会去判断workerQueue是否为空,其他状态(stop,tidying,terminated)的话,不会去判断任务队列是否为空,只有当状态为shutdown并且队列为空的时候,才会退出。

在执行任务过程中,任务执行完或者出现异常中断执行的时候,会调用processWorkerExit方法:

代码逻辑:

  1. 先判断线程是否突然终止,如果是突然终止,则通过CAS操作workCount-1操作;
  2. 统计线程池统计任务数,并将worker从workers中移除;
  3. 判断线程池状态,尝试终止线程池;
  4. 线程池没有成功终止:
  1. 判断是否突然完成任务,不是则进入下一步,是则进行第三步;
  2. 如果允许核心线程池超时,队列不为空,则只是保证一个线程存活;
  3. 添加一个空任务的worker

3.小结:

  1. 线程池的工作线程通过Worker类实现,在ReentrantLock锁的保证下,把Worker实例插入HashSet中,并启动Worker中的线程;
  2. 从Worker类的构造函数可以发现:在线程工厂创建线程thread时,将Worker实例本身this作为参数传入,当执行start方法启动thread时,本质是执行了Worker的runWorker方法;
  3. firstTask执行完成后,通过getTask方法从阻塞队列中获取等待任务,如果任务队列中没有任务,getTask方法会阻塞挂起,不会占用cpu资源。

 

  1. 线程池关闭过程,看shutdown方法:

shutdown方法改变状态为shutdown,并在尝试给每个线程设置中断标志,接着结合getTask方法返回null来停止,移除线程,最后尝试终止线程。

  • Executor框架:

图2 Executor框架的类与接口

  1. Executor是一个接口,它将任务的提交和任务的执行分离开来;
  2. ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务;
  3. ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令(ScheduledThreadPoolExecutor比Timer更加灵活,功能更强大)
  4. Future接口和实现Future接口的实现类FutureTask类,代表异步计算的结果;
  5. Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。

图 2 Executor框架使用示意图

 

1.Executor框架成员

1)CachedThreadPool

CachedThreadPool是通过Executors静态方法创建的ThreadPoolExcutor实例,是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。该线程池在执行时,大量短生命周期的异步任务,可以提高程序性能。调用execute时,可以重用之前已构造的可用线程,如果不存在可用线程,则会重新重新一个新的线程并加入到线程池中。如果线程超过60s还未被使用,就会被中止并从缓存中移除。因此,线程池在长时间空闲后不会消耗任务资源。

CachedThreadPool的线程数是无限增大,构造函数中:

  1. corePoolSize被设置为0,即corePool为空;
  2. maxPoolSize=Integer.MAX,即无界的;
  3. keepAliveTime被设置为60L,空闲线程最长等待时间为60s,如果超时则终止。
  4. 队列的实例采用的是:SynchronousQueue是一个没有容量的阻塞队列,每个插入操作必须等待另一个线程的对应移除操作,反之亦然。例如:对于多个线程,三个线程执行put操作,都会进入阻塞状态;另外线程调用三次take操作,这样才会同时解锁。数据是在配对的生成和消费之间直接传递,并不会在队列中缓存,CachedThreadPool中任务传递示意图如下:

因此,如果新任务来的时候,CachedThreadPool就创建新的线程(极端情况下,会因为创建过多线程而耗尽CPU和内存资源),如果有空闲线程就会重复使用,线程空闲60s就会被回收。

CacheThreadPool工作流程:

  1. 首先执行SynchronousQueue.offer(Runnable.Task)。如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲执线程行poll操作成功匹配,主线程把任务交给空闲线程执行,execute方法执行完成;
  2. 当初始maximumPool为空,或者maximumPool当前没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)。这种情况下,上述步骤会执行失败,CacheThreadPool会创建一个新的线程执行任务,execute方法执行完成;
  3. 在新创建的线程将任务执行完成后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这个poll操作会让空闲线程最多在队列中等待60s。如果60s内主线程提交了一个新的任务,那么这个空闲线程将执行主线程提交的任务;否则,这个空闲线程将会被中止。由于空闲60s的线程会被中止,所以长时间保持空闲的CachedThreadPool不会使用任务资源。

2)FixedThreadPool

FixedThreadPool是通过Executors静态方法创建的ThreadPoolExcutor实例。这个实例会复用固定数量的线程处理一个共享无边界队列。任何时间点,最多有nThreads个线程胡处于活动状态执行任务。如果当所有线程都是在活动时,有多的任务被提交过来,那么会一直在队列中等待知道线程可用。如果任何线程在执行过程中因为错误而终止,新的线程会替代它原来的位置执行后续的任务。所有线程都会一致存于线程池中,直到显示执行ExecutorService.shutdown方法关闭。

FixedThreadPool的corePoolSize和maximumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads;keepAliveTime设置为0L,多余空闲线程会被立即终止。

队列采用的是无边界LinkedBlockingQueue作为线程池的工作队列,其中,队列容量为Integer.MAX_VALUE:

  1. 当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此,线程池不会超过corePoolSize;
  2. 由于使用无界队列时,maximumPoolSize将使一个无效参数;
  3. 同理,keepAliveTime将是一个无效参数;
  4. 由于使用的是无界队列,运行中的FixedThreadPool不会拒绝任务。

3)SingleThreadPool

SingleThreadPool是通过Executors静态方法创建的ThreadPoolExcutor实例。该线程池只会使用单个线程来执行一个无边界队列(如果单个线程在执行过程中因为某些错误中止,新的线程会替代他执行后续任务)。它可以保证认为是按顺序执行的,任何时候都不会有多于一个的线程处于活跃状态。

SingleThreadPool的corePoolSize和maximumPoolSize被设置为1,其他参数与FixedThreadPool相同;

队列同样采用的是无界队列LinkedBlockingQueue作为线程池的工作队列,队列容量大小Integer.MAX_VALUE:

  1. 如果当前运行线程数少于corePoolSize,即线程池中没有运行的线程,则创建一个新线程来执行任务;
  2. 当前线程池有任务执行,则新添加的任务加入到LinkedBlockingQueue;
  3. 线程执行完第一个任务后,会在一个无限循环中反复从LinkedBlockingQueue获取任务来执行。
  • 小demo
  1. corePoolSize、maximumPoolSize以及LinkedBlockingQueue参数相互关系验证:

运行结果如下:

  1. 在执行的线程有:

thread-1

thread-4

  1. 阻塞队列里面等待执行线程有:

thread-2

thread-3

  1. 被拒绝的线程:

       thread-5

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值