使用线程池的好处
- 降低资源消耗。
- 提高响应速度。
- 提高线程的可管理性。
线程池的实现原理
当用户提交了一个任务到线程池,线程池执行流程如下:
- 线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下一个流程。
- 线程池判断工作队列是否已满。如果没满,则将新提交的任务存储在这个工作队列中。如果满了则进入下一个流程。
- 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果满了,则交给饱和策略来处理。
参数解析
常见的构造函数如下:
public ThreadPoolExecutor(
int corePoolSize,int maximumPoolSize, long keepAliveTime,
TimeUnit unit,BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,RejectedExecutionHandler handler)
- corePoolSize。核心线程的数量。即使这些线程是空闲状态也不会被销毁。如果提前调用了线程池的
prestartAllCoreThreads()
方法,线程池会提前创建并启动所有的基本线程。 - maximumPoolSize。线程允许的最大线程数。
- keepAliveTime。如果一个线程处于空闲状态,并且当前线程数量大于 corePoolSize,在 keepAliveTime 时间过后这个空闲线程将会被销毁。
- unit。keepAliveTime 的单位。
- workQueue。工作队列。当新的任务被提交后,会进入该工作队列。
- threadFactory。创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等。
- handler。拒绝策略。
任务队列
- ArrayBlockingQueue。基于数组的有界阻塞队列,先进先出(FIFO)。
- LinkedBlockingQueue。基于链表的阻塞队列,先进先出(FIFO),吞吐量高于 ArrayBlockingQueue。
- SynchronousQueue。一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则一直处于阻塞状态。
- PriorityBlockingQueue。具有优先级的无限阻塞队列。
拒绝策略
有如下几种拒绝策略
- AbortPolicy。直接抛异常。
- CallerRunsPolicy。只有调用者所在线程来运行任务。
- DiscardOldestPolicy。丢弃队列里最近的一个任务(最先进入队列的,最老的),并执行当前任务。
- DiscardPolicy。不处理,丢弃掉。
依次验证。
class DemoThread extends Thread {
int code;
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " running,code " + code);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void setCode(int code) {
this.code = code;
}
}
定义一个线程类,给一个编号 code,在线程运行过程中打印当前线程的名字和编码,然后睡眠3秒。
public static void test(RejectedExecutionHandler rejectedExecutionHandler) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10), new DefaultThreadFactory("demo"), rejectedExecutionHandler);
for (int i = 0; i < 100; i++) {
DemoThread thread = new DemoThread();
thread.setCode(i);
threadPoolExecutor.execute(thread);
}
}
定义一个测试方法,在该方法内定义一个线程池,核心线程数是2,最大线程数是5,任务队列最大值是10,拒绝策略由调用方传入。如:
public static void main(String[] args) {
test(new ThreadPoolExecutor.AbortPolicy());
}
接下来开始测试。
AbortPolicy
测试结果:
demo-1-1 running,code 0
demo-1-2 running,code 1
demo-1-3 running,code 12
demo-1-4 running,code 13
demo-1-5 running,code 14
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task Thread[Thread-15,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@1e80bfe8[Running, pool size = 5, active threads = 5, queued tasks = 10, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at thread.ThreadPoolDemo.test(ThreadPoolDemo.java:25)
at thread.ThreadPoolDemo.main(ThreadPoolDemo.java:16)
demo-1-1 running,code 2
demo-1-4 running,code 5
demo-1-3 running,code 6
demo-1-2 running,code 3
demo-1-5 running,code 4
demo-1-1 running,code 7
demo-1-5 running,code 8
demo-1-4 running,code 9
demo-1-2 running,code 10
demo-1-3 running,code 11
分析:0号任务和1号任务先被核心线程处理,由于核心线程已满,所以2到11号任务被放到阻塞队列中,当阻塞队列满了之后,将会开辟新的线程来处理12、13和14号任务,当15号任务进来之后,由于核心线程池、阻塞队列都满来,并且也达到来最大线程数,此时会执行 AbortPolicy 拒绝策略,直接抛出异常,此时主线程不会再产生任务了。后面线程池将会从任务队列中拿出任务来接着执行。
CallerRunsPolicy
测试结果:
demo-1-1 running,code 0
demo-1-2 running,code 1
demo-1-3 running,code 12
demo-1-4 running,code 13
main running,code 15
demo-1-5 running,code 14
main running,code 16
demo-1-5 running,code 2
demo-1-4 running,code 3
demo-1-2 running,code 5
demo-1-3 running,code 4
demo-1-1 running,code 6
demo-1-5 running,code 7
demo-1-3 running,code 11
main running,code 27
demo-1-2 running,code 10
demo-1-1 running,code 8
demo-1-4 running,code 9
demo-1-5 running,code 17
demo-1-1 running,code 20
demo-1-2 running,code 19
demo-1-3 running,code 18
main running,code 30
…………
分析:分析过程跟之前一样,可以看到15号任务是被 main 线程调用的。值得注意的是,在 mian 线程执行15号任务的时候,后面的任务并没有被生产出来,这种方式可以减缓线程池本身的压力,但是会增加 main 线程的压力。
DiscardOldestPolicy
测试结果:
demo-1-1 running,code 0
demo-1-2 running,code 1
demo-1-3 running,code 12
demo-1-4 running,code 13
demo-1-5 running,code 14
demo-1-4 running,code 90
demo-1-1 running,code 91
demo-1-2 running,code 92
demo-1-3 running,code 94
demo-1-5 running,code 93
demo-1-1 running,code 96
demo-1-4 running,code 95
demo-1-2 running,code 97
demo-1-3 running,code 98
demo-1-5 running,code 99
分析:0号和1号任务正常进入核心线程池,2到11号进入阻塞队列,12、13和14号被普通线程处理,后面直接执行的是第90号任务,说明第2到89号任务都被抛弃了。
DiscardPolicy
测试结果:
demo-1-1 running,code 0
demo-1-2 running,code 1
demo-1-3 running,code 12
demo-1-4 running,code 13
demo-1-5 running,code 14
demo-1-4 running,code 2
demo-1-5 running,code 3
demo-1-1 running,code 6
demo-1-2 running,code 4
demo-1-3 running,code 5
demo-1-2 running,code 7
demo-1-4 running,code 10
demo-1-3 running,code 9
demo-1-5 running,code 8
demo-1-1 running,code 11
分析:这个结果跟上一个结果有点相反的味道,因为从第14号任务之后,所有的任务都被抛弃掉了,但是并没有影响先进入队列里的任务。
参数配置
CPU 密集型任务应配置尽可能小的线程,如配置 N+1 个线程的线程池。
IO 密集型任务线程尽可能多配置线程数量,如 2N,因为这种类型的任务并不是一直在执行任务。
有这样一个公式,核心数 N,task 本地执行时间是 X,等待时间是 Y,那么核心线程可以设置为 N*(X+Y)/X。
可通过Runtime.getRuntime().availableProcessors()
获取当前设备的 CPU 个数。
Executors 创建线程池
newFixedThreadPool
创建一个固定大小的线程池,核心线程数和队列数都是一样固定的。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,0L,
TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
newCachedThreadPool
创建一个具有缓存功能的线程池,核心线程数是 0,但是队列的数量是 Integer.MAX_VALUE,线程超过60s空闲将会销毁。它比较适合处理执行时间比较小的任务。
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L,
TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),threadFactory);
}
SingleThreadExecutor
它只会创建一条工作线程处理任务。
public static ExecutorService newSingleThreadExecutor(){
return new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
ScheduledThreadPool
创建一个调度任务的线程池。
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
线程池的停止
停止线程池有两种 api。
void shutdown()
List<Runnable> shutdownNow()
第一种方式会停止接收新的任务,但是会等已经存在的任务完成才真正的停止。
第二种方式会停止执行正在执行的任务,并返回未执行的任务。