线程池
线程池状态
ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量
- RUNNING 正在运行
- SHUTDOWN 不会接受新任务,但会处理阻塞队列中的任务
- STOP 会中断当前执行的任务,并抛弃阻塞队列
- TIDYING 任务执行完毕
- TERMINATED 终结状态
这些信息存储在一个原子变量 ctl 中,目的是将线程池状态与线程个数合二为一,这样就可以用一次 cas 原子操作进行赋值
构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
-
int corePoolSize
常驻核心线程数,如果为 0,当执行完任务没有任何请求时会消耗线程池;如果大于 0,即使本地任务执行完,核心线程也不会被销毁。该值设置过大会浪费资源,过小会导致线程的频繁 创建与销毁。
-
int maximumPoolSize
线程池能够容纳同时执行的线程最大数,必须大于等于 1,如果与核心线程数
设置相同代表固定大小线程池。 -
long keepAliveTime
超过核心线程数时闲置线程的存活时间。
-
TimeUnit unit
keepAliveTime 的时间单位。
-
BlockingQueue workQueue
工作队列,当线程请求数大于等于 corePoolSize 时线程会进入阻塞队列。
-
threadFactory:
线程工厂,用来生产一组相同任务的线程。可以给线程命名,有利于分析错误。
-
handler:
拒绝策略,默认使用 AbortPolicy 丢弃任务并抛出异常,CallerRunsPolicy 表示重新尝试 提交该任务,DiscardOldestPolicy 表示抛弃队列里等待最久的任务并把当前任务加入队列,DiscardPolicy 表示直接抛弃当前任务但不抛出异常。
线程池的工作方式
1.线程池刚开始没有任务,当一个任务提交给线程池时后,线程池创建一个新线程来执行任务。
2.当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排
队,直到有空闲的线程。
3.如果队列选择了有界队列,当阻塞任务数大于队列的容量时,创建救急线程执行任务(救急线程等于maximumPoolSize-corePoolSize)。
4.如果线程数达到该线程池最大线程数(maximumPoolSize),还有任务未执行,这是会执行拒绝策略。
线程池创建线程时,会将线程封装成工作线程 Worker,Worker 在执行完任务后还会循环获取工作队列中的任务来执行。当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断。如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
创建线程池的方法
可以通过 Executors 的静态工厂方法创建线程池:
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
固定大小的线程池,核心线程数也是最大线程数,不存在救急线程,keepAliveTime = 0。该线程池使用的工作队列是无界阻塞队列 LinkedBlockingQueue,适用于负载较重的服务器。线程不会自动结束。
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
该线程池只有一个线程,相当于单线程串行执行所有任务。任务数大于1时,其他任务会放入无界队列。所有线程执行完毕,该线程也不会释放。如果当前线程执行失败,会终止该线程,新创建一个线程继续执行任务队列的任务。适用于需要保证顺序执行任务的场景。
ExecutorService pool = Executors.newSingleThreadExecutor();
pool.execute(() ->{
log.debug("1");
Sleep.sleep(1000);
int i =1/0;
});
pool.execute(() ->{
log.debug("1");
Sleep.sleep(1000);
//int i =1/0;
});
pool.execute(() ->{
log.debug("1");
Sleep.sleep(1000);
});
单线程线程池与单线程的区别:
自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作。
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
-
核心线程数为0,maximumPoolSize 设置为 Integer 最大值,是高度可伸缩的线程池。也说明该线池中全部都是救急线程(空闲60s后被收回),可以被无限创建。如果线程池大小超过了处理任务所需要的线程,那么就会部分空闲线程(60s内没有执行任务)。当任务数增加时,线程池创建新线程执行任务。
-
该线程池使用的工作队列是没有容量的 SynchronousQueue,如果主线程提交任务的速度高于线程处理
的速度,线程池会不断创建新线程,极端情况下会创建过多线程而耗尽CPU 和内存资源。适用于执行很
多短期异步任务的小程序或负载较轻的服务器。
newScheduledThreadPool
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
核心线程数固定,线程数最大为 Integer 最大值,存在 OOM 风险。支持定期及周期性任务执行,适用需要多个后台线程执行周期任务,同时需要限制线程数量的场景。相比 Timer 更安全,功能更强,与newCachedThreadPool 的区别是不回收工作线程。
//任务调度线程池
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
//延时x秒执行任务
pool.schedule(() ->{
log.debug("task 1 is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},3, TimeUnit.SECONDS);
//延迟一秒执行任务,并每隔一秒执行一次,间隔算入线程的运行时间,
// 假如线程运行时间大于间隔时间,会在线程运行后直接下一次任务
pool.scheduleAtFixedRate(()->{
log.debug("task 1 is running");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},1,1,TimeUnit.SECONDS);
//延迟一秒执行任务,并需要等待任务完成,在等一秒循环执行一次,!
pool.scheduleWithFixedDelay(() ->{
log.debug("task 1 is running");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},1,1,TimeUnit.SECONDS);
}
线程提交任务的方法
// 执行任务
void execute(Runnable command);
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
关闭线程池
可以调用 shutdown 或 shutdownNow 方法关闭线程池,原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法中断线程,无法响应中断的任务可能永远无法终止。
区别是 shutdownNow 首先将线程池的状态设为 STOP,然后尝试停止正在执行或暂停任务的线程,并返回等待执行任务的列表。而 shutdown 只是将线程池的状态设为 SHUTDOWN,然后中断没有正在执行任务的线程。
通常调用 shutdown 来关闭线程池,如果任务不一定要执行完可调用 shutdownNow 。
线程池是如何实现的
所谓的线程池中的“线程”,其实是被抽象为了一个静态内部类 Worker,它基于 AQS 实现,存放在线程池的HashSet workers 成员变量中。
而需要执行的任务则存放在成员变量 workQueue(BlockingQueue workQueue)中。这样,整个线程池实现的基本思想就是:从 workQueue 中不断取出需要执行的任务,放在 Workers 中进行处理。
线程池的好处
- 降低资源消耗,复用已创建的线程,降低开销、控制最大并发数。
- 隔离线程环境,可以配置独立线程池,将较慢的线程与较快的隔离开,避免相互影响。
- 实现任务线程队列缓冲策略和拒绝机制。
- 实现某些与时间相关的功能,如定时执行、周期执行等。
处理线程池的异常
处理线程池中的异常
1.用try-catch块来不住异常,打印错误信息
2.用callable接口实现任务,结果返回给future对象