线程池的优势
首先,线程池是将多个线程进行池化操作,统一进行管理,这样做有什么好处呢?
-
降低创建、销毁线程的开销
:线程池中维护固定数量的线程,不需要临时进行线程的创建和销毁 -
提高响应速度
:对于新提交到线程池中的任务,直接使用线程池中的空闲线程可以直接进行处理,不需要等待创建线程 -
节省资源
:可以重复利用线程。
什么场景下会用到线程池?
一般就是多 IO 的场景下需要用到,像 IO 任务很多,比如数据库操作、请求其他接口操作,这都属于 IO 类任务,IO 类任务的特点就是只需要线程去启动一下 IO 任务,之后就等待 IO 结果返回即可,IO 结果返回的时间是比较慢的 ,因此如果只使用单线程去执行 IO 任务的话,由于这个等待时间比较长,那么线程需要一直等待 IO 结果返回,而无法执行其他操作。
因此在多 IO 场景下,可以使用线程池来加快 IO 任务的执行,开启多个线程同时去启动多个 IO 任务,可以加快 IO 任务的处理速度。
线程池中重要的参数
线程池中重要的参数如下:
-
corePoolSize
:核心线程数量 -
maximumPoolSize
:线程池最大线程数量 = 核心线程数+非核心线程数 -
keepAliveTime
:非核心线程存活时间 -
unit
:空闲线程存活时间单位(keepAliveTime单位) -
workQueue
:工作队列(任务队列),存放等待执行的任务-
LinkedBlockingQueue:无界的阻塞队列,最大长度为 Integer.MAX_VALUE
-
ArrayBlockingQueue:基于数组的有界阻塞队列,按FIFO排序
-
SynchronousQueue:同步队列,不存储元素,对于提交的任务,如果有空闲线程,则使用空闲线程来处理;否则新建一个线程来处理任务
-
PriorityBlockingQueue:具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
-
-
threadFactory
:线程工厂,创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等。 -
handler
:拒绝策略 ,有4种(也可以自定义拒绝策略)-
AbortPolicy :直接抛出异常,默认策略
-
CallerRunsPolicy:用调用者所在的线程来执行任务
-
DiscardOldestPolicy:丢弃阻塞队列里最老的任务,也就是队列里靠前的任务
-
DiscardPolicy :当前任务直接丢弃
-
对于新加入的任务,线程池如何进行处理?
对于新加入一个任务,线程池处理流程如下:
-
如果核心线程数量未达到,创建核心线程执行
-
如果当前运行线程数量已经达到核心线程数量,查看任务队列是否已满
-
如果任务队列未满,将任务放到任务队列
-
如果任务队列已满,看最大线程数是否达到,如果未达到,就新建非核心线程处理
-
如果当前运行线程数量未达到最大线程数,则创建非核心线程执行
-
如果当前运行线程数量达到最大线程数,根据拒绝策略处理。
如何将任务提交到线程池中?
有两种方式:execute
和 submit
这两种方式的区别:
-
execute
-
execute 没有返回值
-
execute 无法捕获任务过程中的异常
-
-
submit
-
submit 会返回一个
Future
对象,用来获取任务的执行结果 -
submit 可以通过 Future 对象来捕获任务中的异常
-
execute 方式如下:
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(new Runnable() {
public void run() {
// 执行具体的任务逻辑
System.out.println("Task executed using execute method");
}
});
executor.shutdown();
submit 方式如下:
ExecutorService executor = Executors.newFixedThreadPool(5);
Future<String> future = executor.submit(new Callable<String>() {
public String call() {
// 执行具体的任务逻辑
return "Task executed using submit method";
}
});
try {
String result = future.get(); // 获取任务执行结果
System.out.println(result);
} catch (InterruptedException e) {
// 处理中断异常
} catch (ExecutionException e) {
// 处理任务执行异常
} finally {
// 关闭线程池
executor.shutdown();
}
线程池是如何关闭的?
通过调用线程池的 shutdown()
方法即可关闭线程池
调用之后,会设置一个标志位表示当前线程池已经关闭,会禁止向线程池中提交新的任务
去中断所有的空闲线程并且等待正在执行的任务执行完毕(通过调用线程 interrupt()
方法),当线程池中所有任务都执行完毕之后,线程池就会被完全关闭。
扩展:thread.interrupt() 方法调用后线程不会立即中断。调用 interrupt 只是将被中断线程的中断状态设置为 true,通知被中断的线程自己处理中断,而不是立即强制的让线程直接中断(强制中断不安全)
当外部调用线程进行中断的命令时,如果该线程处于被阻塞的状态,如 Thread.sleep(),Object.wait(),BlockingQueue#put,BlockingQueue#take 等等时,那么此时调用该线程的 interrupt 方法就会抛出 InterruptedException 异常
因此,可以通过这个特点来优雅的停止线程(在 《Java多线程核心技术》 一书中说到):将 sleep() 和 interrupt() 搭配使用,来停止线程。