【Java并发编程的艺术】读书笔记——Java中的线程池实现原理

学习参考资料:《Java并发编程的艺术》

1、为什么要使用线程池(优点)

  • 避免重复的进行线程的创建和摧毁,可以减少不必要的资源开销
  • 任务到达后,可以直接获取线程执行,提高了响应速度
  • 可以交给线程池统一进行管理,提高线程的可管理性

2、线程池的实现原理

当向线程池提交一个任务时,线程池是怎样处理这个任务的呢?
ThreadPoolExecutor主要执行execute()来实现的,如下图:

在这里插入图片描述

1)看线程池中的线程数量是否达到核心线程数;没有就创建线程执行任务,否则继续执行 2
2)将任务添加到阻塞队列中,等待线程处理;如果阻塞队列已经满了,执行 3
3)如果线程池中线程的数量没有超过最大线程数,就创建线程执行任务,否则执行 4
4)执行提前设置好的饱和策略

注意:

  • 线程在执行完当前任务时,会不断从阻塞队列中获取任务来执行
  • 当一个线程空闲了一定时间&&线程池中线程数>核心线程数就会被摧毁

在这里插入图片描述

3、线程池的创建以及核心参数

我们可以通过ThreadPoolExecutor来创建一个线程池:

	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {}

线程池的七个参数

  1. corePoolSize:核心线程数。
  2. maximumPoolSize:最大线程数。
  3. keepAliveTime:线程存活时间。当线程数大于core数,那么超过该时间的线程将会被终结。
  4. unit:keepAliveTime的单位。java.util.concurrent.TimeUnit类存在静态静态属性: NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS
  5. workQueue:Runnable的阻塞队列。若线程池已经被占满,则该队列用于存放无法再放入线程池中的Runnable。
  6. threadFactory:创建一个新线程时使用的工厂,可以用来设定线程名、是否为守护线程等等
  7. handler:拒绝策略

4、如何合理地创建线程池

要想合理的配置线程池的大小,首先得分析任务的特性,可以从以下几个角度分析:

  1. 任务的性质:CPU密集型任务、IO密集型任务、混合型任务。
  2. 任务的优先级:高、中、低。
  3. 任务的执行时间:长、中、短。
  4. 任务的依赖性:是否依赖其他系统资源,如数据库连接等。

性质不同的任务可以交给不同规模的线程池执行。

对于不同性质的任务来说,CPU密集型任务应配置尽可能小的线程,如配置CPU个数+1的线程数,IO密集型任务应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,如配置两倍CPU个数+1,而对于混合型的任务,如果可以拆分,可以拆分成IO密集型和CPU密集型分别处理。

若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置得越大,才能更好的利用CPU。

在《java并发编程实践》中有一个估算最佳线程数合理值的公式:

Nthreads=Ncpu*Ucpu*(1+w/c)
其中
Ncpu=CPU核心数
Ucpu=cpu使用率,0~1
W/C=等待时间与计算时间的比率

IO密集型:一般情况下,如果存在IO,那么肯定w/c>1(阻塞耗时一般都是计算耗时的很多倍),但是需要考虑系统内存有限(每开启一个线程都需要内存空间),这里需要上服务器测试具体多少个线程数适合(CPU占比、线程数、总耗时、内存消耗)。如果不想去测试,保守点取1即,Nthreads=Ncpu*(1+1)=2Ncpu。这样设置一般都OK。

计算密集型:假设没有等待w=0,则W/C=0. Nthreads=Ncpu。

上面只是给了大概的配置线程池的参考,但是如果在实际开发中要具体确定合理地线程池值大小,还是需要结合系统实际情况,通过压力测试来进行微调。


版权声明:本文为weixin_46804107原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。