线程池

Thread类

new Thread的弊端

  1. 每次new Thread新建对象,性能差。
  2. 线程缺乏统一的管理,可能无限制地新建线程,相互竞争,有可能占用更多的系统资源导致宕机或者OOM。
  3. 缺少更多高级功能,如:更多执行、定期执行,线程中断等。

线程池

线程池的好处

  1. 重用存在的线程,减少对象的创建、销毁的开销,性能好。
  2. 可以有效地控制最大并发的线程数量,提高系统资源利用率,同时可以避免过多的资源竞争,避免阻塞。
  3. 提供定期执行、定时执行、单线程、并发控制等功能。

ThreadPoolExecutor

初始化的参数
  1. corePoolSize:核心线程数量;
  2. maximumPoolSize:最大线程数量;
  3. workQueue:阻塞队列,存储等待执行的任务,对线程池的运行过程有重大影响。
  4. keepAliveTime:线程中无任务空转,但仍保持存活的延期时间。当目前线程池中的线程数量大于等于corePoolSize时才有效。
  5. unit:是keepAliveTime的时间单位。
  6. threadFactory:即用来创建线程的工厂。未指定则会使用默认的threadFactory来创建线程:线程优先级相同、非守护线程、默认递增的名字。
  7. rejectedExecutionHandler:拒绝策略。

四种拒绝策略:

  1. ThreadPoolExecutor.AbortPolicy:默认策略,直接抛出异常。
  2. ThreadPoolExecutor.CallerRunsPolicy:调用目前所在的线程来执行任务。
  3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中最早的一个线程中的任务,并执行当前的任务。
  4. ThreadPoolExecutor.DiscardPolicy:直接丢弃该任务。
初始化参数间的关系
  1. 当目前线程池中的线程数量小于corePoolSize时,且有新的线程创建请求时,直接创建新的线程。
  2. 当目前线程池中的线程数量大于等于corePoolSize,但小于maximumPoolSize时,若此时workQueue未满,则该任务会被放在workQueue中等待;若此时workQueue已经满了,则会为该任务直接创建新的线程。
  3. 当目前线程池中的线程数量大于maximumPoolSize时,如果workQueue未满则将线程请求将被放在阻塞队列workQueue中;如果workQueue已满,则会根据指定参数rejectHandler采用相应的拒绝策略,来处理线程请求。

workQueue是一个保存任务的阻塞队列。根据上面的三种情况,该workQueue对应的处理也有三种:

  1. 直接切换:默认使用SynchronousQueue实现。
  2. 使用无界队列LinkedBlockingQueue,此时可以创建的最多线程数即是corePoolSize,maximumPoolSize则不会起作用了。
  3. 使用有界队列,常用ArrayBlockingQueue,可以指定队列的maximumPoolSize,但会使线程调度更加困难。因此推荐使用无界队列。

ThreadPoolExecutor的状态

图示

  1. RUNNING:运行中。可以接受新提交的任务,或处理阻塞队列中的任务。
  2. SHUTDOWN:关闭。不能接受新提交的任务,但可以继续处理阻塞队列中的任务。
  3. STOP:停止。既不能接受新提交的任务,但可以继续处理阻塞队列中的任务,并中断正在进行中的线程。
  4. TIDYING:整理中。在SHUTDOWN时,当阻塞队列为空且线程池中工作的线程数量为0时,或在STOP时,线程池中工作的线程数量为0时,转为该状态,并整理线程池中的内容。此时等待线程池被终结。
  5. TERMINATED:TIDYING时调用terminated()方法,进入TERMINATED。即线程池被销毁。

ThreadPoolExecutor的方法成员

图示

  1. execute():执行任务,由线程池运行。
  2. submit():提交任务,可以返回执行的结果。即execute()方法,配合Future的结合。
  3. shutdown():关闭线程池(不是立刻关闭)。不接受新提交的任务,但可以继续处理阻塞队列中的任务;当任务执行完毕才会关闭线程池。
  4. shutdownNow():立即关闭线程池。不接受新提交的任务,中断正在执行中的线程,丢弃阻塞队列中的任务。
  5. getTaskCount():返回线程池中已执行和未执行的任务总数。
  6. getCompletedTaskCount():返回已经执行完毕的任务数量。
  7. getPoolSize():返回目前线程池中的线程数量。(因存在线程间的动态切换,故返回值是近似值)
  8. getActiveCount():返回线程池中正在执行任务的线程数量。(因存在线程间的动态切换,故返回值是近似值)

ThreadPoolExecutor的类图

图示

其中左上角部分:
Executor:一个运行线程任务的简单接口。
ExecutorService:拓展了Executor:添加了管理执行器生命周期、任务生命周期的方法。
ScheduledExecutorService:拓展了ExecutorService:支持Future和定期执行任务。

Executors类

该类是针对Executor接口提供的工厂方法和工具方法。

Executors类内部方法:

图示

  1. Executors的newCachedThreadPool()方法:创建一个容量大小可变的线程池:根据需要回收线程或新建线程。
  2. Executors的newFixedThreadPool(int nThreads)方法,创建一个固定大小的线程池。若线程池已满则阻塞在workQueue中。
  3. Executors的newScheduledThreadPool(int corePoolSize)方法,也是固定大小的线程池,但支持定时执行或周期执行任务。
  4. Executors的newSingleThreadScheduledExecutor()方法:单线程的线程池,可以指定优先级或FIFO/LIFO。

看看源码

newCachedThreadPool():(有重载的指定threadfactory的方法)

1
2
3
4
5
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

newFixedThreadPool(int nThreads):(有重载的指定threadfactory的方法)

1
2
3
4
5
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

其他的几个线程池的方法类似,都是调用ThreadPoolExecutor的构造函数。线程池返回的值都是ExecutorService实例。

演示例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Slf4j
public class ThreadPoolExample4 {

public static void main(String[] args) {

ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);

executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
log.warn("schedule run");
}
}, 1, 3, TimeUnit.SECONDS);

Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
log.warn("timer run");
}
}, new Date(), 5 * 1000);
}
}
运行结果:
1
2
3
4
5
6
7
8
00:53:53.070 [Timer-0] WARN com.mmall.concurrency.threadPool.ThreadPoolExample4 - timer run
00:53:54.066 [pool-1-thread-1] WARN com.mmall.concurrency.threadPool.ThreadPoolExample4 - schedule run
00:53:57.068 [pool-1-thread-1] WARN com.mmall.concurrency.threadPool.ThreadPoolExample4 - schedule run
00:53:58.067 [Timer-0] WARN com.mmall.concurrency.threadPool.ThreadPoolExample4 - timer run
00:54:00.067 [pool-1-thread-1] WARN com.mmall.concurrency.threadPool.ThreadPoolExample4 - schedule run
00:54:03.067 [pool-1-thread-1] WARN com.mmall.concurrency.threadPool.ThreadPoolExample4 - schedule run
00:54:03.067 [Timer-0] WARN com.mmall.concurrency.threadPool.ThreadPoolExample4 - timer run
00:54:06.068 [pool-1-thread-1] WARN com.mmall.concurrency.threadPool.ThreadPoolExample4 - schedule run
例子分析

ExecutorService通过调用scheduleAtFixedRate方法,使该线程池配合定时器,以一个固定的频率调度执行。
例中定义了一个定时器Timer,在其调度方法schedule中新建TimerTask实例,并给出循环执行的时间间隔period,单位为毫秒。

合理配置

  1. 当执行CPU密集型任务时,应尽量压榨使用CPU资源,参考设定线程数量为nCPU+1。
  2. 当执行IO密集型任务时,因其为输入输出两通路,线程数量参考设置为2*nCPU。
  3. 实际中,根据业务需要进行相应调整。
SupriseMF wechat
欢迎关注微信订阅号【星球码】,分享学习编程奇淫巧技~
喜欢就支持我呀(*^∇^*)~

热评文章