核心、救急线程概念
线程池中的线程分为核心线程和救急线程两种
当阻塞队列已满并且所有核心线程都在工作的时候,再来一个任务并不会直接走拒绝策略,而是先上救急线程处理这个孤儿任务,如果没有救急线程可以救他,那么该任务就会走拒绝策略(前提是该阻塞队列是有界的!!!)
核心线程
线程池中始终保持活动的线程数量。即使核心线程处于空闲状态,它们也不会被销毁,以便能够快速响应任务的到来。核心线程数量可以通过线程池的corePoolSize参数来设置。
救急线程
线程池中能够创建的最大线程数量。当线程池中的任务队列已满且其余核心线程都在执行任务时,线程池会创建新的线程来处理任务,直到达到最大线程数。救急线程数量可以通过线程池的maximumPoolSize参数来设置。
二者区别
核心线程和救急线程的区别在于:
- 核心线程始终保持活动状态,不会被销毁,以便能够快速响应任务的到来;而救急线程是在任务队列已满且核心线程都在执行任务时才会创建的,任务处理完后,在自定义的时间后,空闲的救急线程会被销毁。
- 核心线程数量是线程池中始终存在的线程数量,而救急线程数量是线程池中能够创建的最大线程数量。(线程池大小 - 核心线程数)
原始线程池的构造方法
常用线程池及其适用场景
根据线程池的构造方法,JDK Executors 类中提供了众多工厂方法来创建各种用途的线程池
(主要是因为构造方法太繁杂,所以说给你一些类似于枚举对象的模式来挑选大方向的类别,自己再自定义决定部分参数)
FixedThreadPool:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
CachedThreadPool:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
SingleThreadExecutor:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
ScheduledExecutor:
在『任务调度线程池 ScheduledExecutor 』功能加入之前,可以使用 java.util.Timer 来实现定时功能, Timer 的优点在于简单易用,但由于 所有任务都是由同一个线程来调度 ,因此所有任务都是 串行执行 的,同一时间只能有一个任务在执行,前一个 任务的延迟或异常都将会影响到之后的任务 。ScheduledExecutor相较于 Timer 的优势在于,它可以通过设置线程池的大小来决定处理任务的消费者个数,并且任务链路上的某个节点抛出异常 不会影响到后续任务。
执行单次的延时任务
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
// 添加两个任务,希望它们都在 1s 后执行
executor.schedule(() -> {
System.out.println("任务1,执行时间:" + new Date());
try { Thread.sleep(2000); } catch (InterruptedException e) { }
}, 1000, TimeUnit.MILLISECONDS);
executor.schedule(() -> {
System.out.println("任务2,执行时间:" + new Date());
}, 1000, TimeUnit.MILLISECONDS);
周期性循环执行任务
// 1s延时后开始进行周期为1s的循环执行该任务
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
log.debug("start...");
pool.scheduleAtFixedRate(() -> {
log.debug("running...");
}, 1, 1, TimeUnit.SECONDS); // 第一个1表示延时时间 第二个1表示循环周期时间
线程池常用API
提交任务
(生产者将任务丢到队列中,让线程池的线程消费者拿去消费)
关闭线程池
shutdown
/*
线程池状态变为 SHUTDOWN
- 不会接收新任务
- 但已提交任务会执行完
- 此方法不会阻塞调用线程的执行
*/
void shutdown();
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 修改线程池状态
advanceRunState(SHUTDOWN);
// 仅会打断空闲线程
interruptIdleWorkers();
onShutdown(); // 扩展点 ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试终结(没有运行的线程可以立刻终结,如果还有运行的线程也不会等)
tryTerminate();
}
shutdownNow
/*
线程池状态变为 STOP
- 不会接收新任务
- 会将队列中的任务返回
- 并用 interrupt 的方式中断正在执行的任务
*/
List<Runnable> shutdownNow();
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 修改线程池状态
advanceRunState(STOP);
// 打断所有线程
interruptWorkers();
// 获取队列中剩余任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
// 尝试终结
tryTerminate();
return tasks;
}
其它方法
// 不在 RUNNING 状态的线程池,此方法就返回 true
boolean isShutdown();
// 线程池状态是否是 TERMINATED
boolean isTerminated();
// 调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事情,可以利用此方法等待
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
正确处理执行任务异常
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(() -> {
log.debug("task1");
int i = 1 / 0;
}
});
如上图,当我们执行到 “int i = 1 / 0;” 代码的时候,正常来说在控制台会打印出异常信息,但是由于在使用ExecutorService执行任务时,如果任务中出现异常,并且没有在任务中进行捕获和处理,异常信息将会被线程池内部捕获并记录。这样做是为了防止异常在任务线程中抛出后终止线程的执行,从而影响线程池的正常运行。
默认情况下,线程池内部会使用
java.util.logging
记录异常信息,而不是直接将异常信息打印到控制台。如果你想要在控制台上看到异常信息,可以参考如下方法
主动捉异常
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(() -> {
try {
log.debug("task1");
int i = 1 / 0;
} catch (Exception e) {
log.error("error:", e);
}
});
21:59:04.558 c.TestTimer [pool-1-thread-1] - task1
21:59:04.562 c.TestTimer [pool-1-thread-1] - error:
java.lang.ArithmeticException: / by zero
at cn.itcast.n8.TestTimer.lambda$main$0(TestTimer.java:28)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
使用 Future
ExecutorService pool = Executors.newFixedThreadPool(1);
Future<Boolean> f = pool.submit(() -> {
log.debug("task1");
int i = 1 / 0;
return true;
});
log.debug("result:{}", f.get());
21:54:58.208 c.TestTimer [pool-1-thread-1] - task1
Exception in thread "main" java.util.concurrent.ExecutionException:
java.lang.ArithmeticException: / by zero
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at cn.itcast.n8.TestTimer.main(TestTimer.java:31)
Caused by: java.lang.ArithmeticException: / by zero
at cn.itcast.n8.TestTimer.lambda$main$0(TestTimer.java:28)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)