java–基础–17.5–线程–线程池
1、介绍
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
2、常用的方法
2.2、创建线程池
newFixedThreadPool: 创建固定线程数量的线程池
newSingleThreadExecutor: 创建单一线程的池
newCachedThreadPool: 创建线程数量自动扩容, 自动销毁的线程池
newScheduledThreadPool: 创建支持计划任务的线程池
2.2、其他方法
2.3、案例
2.1.1、线程池1
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 5; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
//创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
ExecutorService es= Executors.newFixedThreadPool(3);
//提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
es.submit(new MyRunnable());
es.submit(new MyRunnable());
//启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
es.shutdown();
输出:
pool-1-thread-2:0
pool-1-thread-1:0
pool-1-thread-1:1
pool-1-thread-1:2
pool-1-thread-2:1
pool-1-thread-2:2
pool-1-thread-1:3
pool-1-thread-1:4
pool-1-thread-2:3
pool-1-thread-2:4
2.1.2、线程池2:另外一种创建多线程的方式
//Callable:是带泛型的接口。
//这里指定的泛型其实是call()方法的返回值类型。
//返回值类型是Object 的call
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
for (int x = 0; x < 5; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
return null;
}
}
//创建线程池对象
ExecutorService es=Executors.newFixedThreadPool(2);
//可以执行Runnable对象或者Callable对象代表的线程
es.submit(new MyCallable());
es.submit(new MyCallable());
//结束
es.shutdown();
输出:
pool-1-thread-1:0
pool-1-thread-1:1
pool-1-thread-1:2
pool-1-thread-1:3
pool-1-thread-2:0
pool-1-thread-2:1
pool-1-thread-2:2
pool-1-thread-2:3
pool-1-thread-2:4
pool-1-thread-1:4
2.1.3、线程池3
//Callable:是带泛型的接口。
//这里指定的泛型其实是call()方法的返回值类型。
//返回值类型是Boolean的call
public class MyCallable implements Callable<Boolean> {
int number;
public MyCallable(int number) {
this.number = number;
}
public Boolean call() throws Exception {
if(number%2==0){
return true;
}
return false;
}
ExecutorService es=Executors.newFixedThreadPool(2);
Future<Boolean> a=es.submit(new MyCallable(2));
Future<Boolean> b=es.submit(new MyCallable(3));
System.out.println(a.get());
System.out.println(b.get());
es.shutdown();
输出:
true
false
3、运行原理
3.1、创建线程池
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:线程空闲时间
- unit:线空闲时间单位
- workQueue:缓冲队列
- handler:拒绝任务的处理策略
3.2、运行原理
3.2.1、当一个任务通过execute(Runnable)方法添加到线程池时
- 如果此时线程池中的数量小于corePoolSize,创建新的线程来处理被添加的任务。
- 如果此时线程池中的数量等于 corePoolSize,则新任务被添加到workQueue队列中,直到workQueue队列满,但不超过maximumPoolSize。
- 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
- 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
3.2.2、注意事项
- 若线程空闲时间超过keepAliveTime,非核心线程会被回收。
- 若allowCoreThreadTimeOut为true则核心线程也会被回收。
- 默认情况下,核心线程不会被回收。
/**
* If false (default), core threads stay alive even when idle.
* If true, core threads use keepAliveTime to time out waiting
* for work.
*/
private volatile boolean allowCoreThreadTimeOut;
/**
* Timeout in nanoseconds for idle threads waiting for work.
* Threads use this timeout when there are more than corePoolSize
* present or if allowCoreThreadTimeOut. Otherwise they wait
* forever for new work.
*/
private volatile long keepAliveTime;
3.3、潜在宕机风险
使用Executors来创建要注意潜在宕机风险
3.3.1、FixedThreadPool和SingleThreadPoolPool
允许的请求队列长度为 Integer.MAX_VALUE,可能因为无限制任务队列而耗尽资源,只是出现问题的概率较小。如果新请求的到达速率超过了线程池的处理速率,那么新到来的请求将被累积起来可能会堆积大量的请求,从而导致 OOM(内存溢出)。
解决方法:
使用有界队列可以防止资源耗尽,但也因此必须要考虑拒绝策略。因为默认的AbortPolicy策略可能不是我们想要的
3.3.2、CachedThreadPool和ScheduledThreadPool
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
解决方法:
这种情况可以采用固定大小的线程池来解决这个问题。
可能有大量请求的线程池场景中,更推荐自定义ThreadPoolExecutor来创建线程池
3.4、线程池大小配置
- 一般根据任务类型进行区分,假设CPU为N核
- CPU密集型任务:需要减少线程数量,降低线程切换开销,可配置线程池大小为N + 1。
- IO密集型任务:可以加大线程数量,可配置线程池大小为 N * 2。
- 混合型任务:可以拆分为CPU密集型与IO密集型,独立配置。
3.5、案例–FixedThreadPool
3.5.1、代码
# 方法1 :
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
# 方法2 :方法1相比多了一个ThreadFactory类
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
# ThreadPoolExecutor方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);
}
拒绝策略:AbortPolicy,抛出运行时异常RejectedExecutionException。
这种策略丢弃任务,并抛出异常。(jdk默认策略)
private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();
3.5.2、总结
- 线程池的初始值和最大值都设置为了指定的线程数量nThreads。
- keepAliveTime设置成0表示多余的空闲线程会被立即终止。
- 线程池的构造参数中还传入了一个阻塞队列,该阻塞队列的作用其实就是作为一个任务的缓冲区。
4、任务队列
BlockingQueue workQueue:用于存放Runnable对象。
4.1、直接提交(如SynchronousQueue)
- 线程池不对任务进行缓存。新进任务直接提交给线程池,当线程池中没有空闲线程时,创建一个新的线程处理此任务。
- 这种策略需要线程池具有无限增长的可能性。
- Executors.newCachedThreadPool()使用SynchronousQueue创建线程池。
4.1.1、SynchronousQueue
不保存提交的任务,数据也不会缓存到队列中,用于生产者和消费者互等对方,一起离开。
4.2、无界队列(如不具有预定义容量的LinkedBlockingQueue)
- LinkedBlockingQueue将导致当所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)
- 当每个任务执行互不影响时,适合于使用无界队列。
- Executors.newFixedThreadPool(3):使用LinkedBlockingQueue创建线程池。
- Executors.newSingleThreadExecutor():使用LinkedBlockingQueue创建线程池。
4.2.1、LinkedBlockingQueue
使用链表实现的先进先出队列
默认大小为Integer.MAX_VALUE
4.3、有界队列(如ArrayBlockingQueue)
- 防止资源耗尽
- 当最大线程数有限时,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷。
- ArrayBlockingQueue:先进先出队列,创建时指定大小,有界;
4.3.1、ArrayBlockingQueue
先进先出队列
创建时指定大小
有界
4.4、优先级队列(如PriorityBlockingQueue)
PriorityBlockingQueue: 支持优先级的队列
4.5、并发队列-无界阻塞延迟队列(DelayedWorkQueue)
- DelayedWorkQueue是ScheduledThreadPoolExecutor的静态内部类。
- Executors.newScheduledThreadPool(3)使用DelayedWorkQueue创建线程池。
5、拒绝策略
接口RejectedExecutionHandler提供了拒绝任务处理的自定义方法。在ThreadPoolExecutor中已经包含四种拒绝策略。
- AbortPolicy
- 默认的拒绝策略
- 丢弃任务,并抛出RejectedExecutionException异常
- DiscardPolicy
- 不能执行的任务将被丢弃。
- 丢弃当前将要加入队列的任务本身
- DiscardOldestPolicy
- 在pool没有关闭的前提下,抛弃下一个将被执行的任务(工作队列头部),然后重新尝试运行该任务。
- 如果工作队列是一个优先队列,那么该策略将抛弃优先级最高的任务,因此最好不要将DiscardOldestPolicy策略和优先级队列放在一起使用)
- 丢弃任务队列中最旧任务
- CallerRunsPolicy
- 该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者
- 当线程池中所有线程都被占用,并且工作队列被填满后,下一个任务会在调用executor时在主线程中执行,不进入线程池执行。
- 此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
- 该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者
5.1、4种策略的测试代码
public class Worker implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
}
public static void main(String[] args) {
//1 AbortPolicy 将抛出 RejectedExecutionException.
// RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
// 2 DiscardPolicy 用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。运行结果也不会全部执行100个线程。
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
// 3 DiscardOldestPolicy
//在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务(工作队列头部),然后重新尝试运行该任务
//这样运行结果就不会有100个线程全部被执行
// RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
// 4 CallerRunsPolicy 线程调用运行该任务的 execute 本身
// RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
subTask(handler);
}
public static void subTask(RejectedExecutionHandler handler){
int corePoolSize = 5;
int maxPoolSize = 10;
long keepAliveTime = 5;
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(10);
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS,
queue, handler);
for (int i = 0; i < 60; i++) {
executor.execute(new Worker());
}
executor.shutdown();
}
}
5.2、AbortPolicy
- 默认的拒绝策略
- 丢弃任务,并抛出RejectedExecutionException异常
pool-1-thread-10 is running
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task Test.Worker@761f568f rejected from java.util.concurrent.ThreadPoolExecutor@2830803a[Running, pool size = 10, active threads = 9, queued tasks = 0, completed tasks = 28]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2048)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372)
at Test.Worker.subTask(Worker.java:38)
at Test.Worker.main(Worker.java:28)
5.3、DiscardPolicy
- 不能执行的任务将被丢弃。
- 丢弃当前将要加入队列的任务本身
5.4、DiscardOldestPolicy
- 在pool没有关闭的前提下,抛弃下一个将被执行的任务(工作队列头部),然后重新尝试运行该任务。
- 如果工作队列是一个优先队列,那么该策略将抛弃优先级最高的任务,因此最好不要将DiscardOldestPolicy策略和优先级队列放在一起使用)
- 丢弃任务队列中最旧任务
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
5.5、CallerRunsPolicy
- 该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者
- 当线程池中所有线程都被占用,并且工作队列被填满后,下一个任务会在调用executor时在主线程中执行,不进入线程池执行。
- 此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
输出:
pool-1-thread-5 is running
pool-1-thread-8 is running
pool-1-thread-6 is running
main is running
main is running
main is running
main is running