java线程池详解

Posted Java技术前线

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java线程池详解相关的知识,希望对你有一定的参考价值。

与你一起成长~



随着cpu核数越来越多,不可避免的利用多线程技术以充分利用其计算能力。所以,多线程技术是服务端开发人员必须掌握的技术。

线程的创建和销毁,都涉及到系统调用,比较消耗系统资源,那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?所以就引入了线程池技术,避免频繁的线程创建和销毁。

在Java用有一个Executors工具类,可以为我们创建一个线程池,其本质就是new了一个ThreadPoolExecutor对象。线程池几乎也是面试必考问题。本文结合源代码,说说ThreadExecutor的工作原理。

ThreadPoolExecutor类

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。

在ThreadPoolExecutor类中提供了四个构造方法:

 
   
   
 
  1. public class ThreadPoolExecutor extends AbstractExecutorService {

  2. .....

  3. public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

  4. BlockingQueue<Runnable> workQueue);


  5. public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

  6. BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);


  7. public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

  8. BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);


  9. public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

  10. BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);

  11. ...

从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

下面解释下一下构造器中各个参数的含义:


  • corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;



  • maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;



  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;



  • unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:


 
   
   
 
  1. TimeUnit.DAYS; //天

  2. TimeUnit.HOURS; //小时

  3. TimeUnit.MINUTES; //分钟

  4. TimeUnit.SECONDS; //秒

  5. TimeUnit.MILLISECONDS; //毫秒

  6. TimeUnit.MICROSECONDS; //微妙

  7. TimeUnit.NANOSECONDS; //纳秒

  • workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:

 
   
   
 
  1. ArrayBlockingQueue;

  2. LinkedBlockingQueue;

  3. SynchronousQueue;

ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。

  • threadFactory:线程工厂,主要用来创建线程;

  • handler:表示当拒绝处理任务时的策略,有以下四种取值:

 
   
   
 
  1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

  2. ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

  3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

  4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

这里用一个图来说明线程池的执行流程

java线程池详解

任务被提交到线程池,会先判断当前线程数量是否小于corePoolSize,如果小于则创建线程来执行提交的任务,否则将任务放入workQueue队列,如果workQueue满了,则判断当前线程数量是否小于maximumPoolSize,如果小于则创建线程执行任务,否则就会调用handler,以表示线程池拒绝接收任务。

这里以jdk1.8.0_111的源代码为例,看一下具体实现

1、先看一下线程池的executor方法

java线程池详解


  • 判断当前活跃线程数是否小于corePoolSize,如果小于,则调用addWorker创建线程执行任务



  • 如果不小于corePoolSize,则将任务添加到workQueue队列。



  • 如果放入workQueue失败,则创建线程执行任务,如果这时创建线程失败(当前线程数不小于maximumPoolSize时),就会调用reject(内部调用handler)拒绝接受任务。


2、再看下addWorker的方法实现

java线程池详解

这块代码是在创建非核心线程时,即core等于false。判断当前线程数是否大于等于maximumPoolSize,如果大于等于则返回false,即上边说到的③中创建线程失败的情况。

addWorker方法的下半部分:

java线程池详解


  • 创建Worker对象,同时也会实例化一个Thread对象。



  • 启动启动这个线程


3、再到Worker里看看其实现

java线程池详解

可以看到在创建Worker时会调用threadFactory来创建一个线程。上边的②中启动一个线程就会触发Worker的run方法被线程调用。

4、接下来咱们看看runWorker方法的逻辑

java线程池详解线程调用runWoker,会while循环调用getTask方法从workerQueue里读取任务,然后执行任务。只要getTask方法不返回null,此线程就不会退出。

5、最后在看看getTask方法实现

java线程池详解


  • 咱们先不管allowCoreThreadTimeOut,这个变量默认值是false。wc>corePoolSize则是判断当前线程数是否大于corePoolSize。



  • 如果当前线程数大于corePoolSize,则会调用workQueue的poll方法获取任务,超时时间是keepAliveTime。如果超过keepAliveTime时长,poll返回了null,上边提到的while循序就会退出,线程也就执行完了。


如果当前线程数小于corePoolSize,则会调用workQueue的take方法阻塞在当前。

参考资料

https://www.cnblogs.com/dolphin0520/p/3932921.html

- end -


用心分享面试知识,做有温度的攻城狮

每天记得对自己说:你是最棒的!

往期推荐:

888G面试资源



io




                     每一个“好看”,都是对我们最大的肯定!

以上是关于java线程池详解的主要内容,如果未能解决你的问题,请参考以下文章

Java 线程池详解

Java线程池详解

Java线程池详解

Java——线程池

newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段

Java多线程:线程池详解