目录
线程池的作用?
线程池的意义:
线程池的参数
编辑
线程池任务执行的顺序
线程池拒绝策略
四种策略
应用场景分析
AbortPolicy
DiscardPolicy
DiscardOldestPolicy
CallerRunsPolicy
线程池的作用?
优化系统架构通常包括在时间和空间之间进行权衡。对于线程池的创建,采用线程池预先创建并维护一定数量的线程,以空间换取时间的方式,可以有效减少每次任务执行时创建线程所带来的开销。
通过预先创建一定数量的线程并将其放入线程池中,可以避免在任务到来时动态创建线程的开销。相反,任务可以直接从线程池中获取可用的线程并执行,这样可以显著降低任务处理的延迟时间。这种池化思想利用了空间来存储预先创建的线程,以提高整体系统性能。
通过将线程池用作线程的缓存,可以避免重复创建和销毁线程的开销,提高系统的吞吐量和响应性能。此外,线程池还可以根据系统负载的变化动态调整线程的数量,以提供更好的性能和资源利用率。
因此,通过采用线程池来预先创建和维护线程的方式,可以在一定程度上优化系统架构,通过牺牲一部分空间,来换取更高的执行效率和响应能力。
线程池的意义:
-
降低资源的消耗:线程的创建和销毁需要消耗一定的计算机资源,包括内存和CPU时间。通过线程池,可以避免重复地创建和销毁线程,提高资源的利用率。
-
提高响应的速度:线程池中的线程已经预先创建并准备好执行任务。当任务到达时,可以直接分配一个可用的线程来处理,而不需要等待线程创建。这样可以大大降低任务的响应时间,提供更快的服务。
-
方便管理:线程池提供了对线程的统一管理和调度。通过线程池的接口,可以方便地控制线程的数量、设置线程的优先级、监测线程的执行状态等。此外,线程池中的线程通常有一个名称,方便在日志或调试信息中进行追踪和定位,从而方便排查问题和进行故障诊断。
线程池的参数
最小线程数:项目启动的时候,初始化的线程数 1
核心线程数:当线程池的线程都忙碌时,再进来新任务时,由最小线程数扩容到核心线程数 4
最大线程数 8:当核心线程数满了,当队列也满了,再来新任务,就会创建新线程来执行这个新任务,直到线程数达到最大线程数
队列大小 1000:当线程数达到核心线程数,再进来的任务就会进入到队列。
线程同一时刻只能做一件事,如下图需要等待上一步完成才能进行下一步
现在采用多线程,就可以同一时刻去进行多个任务,等最慢的线程结束就认为当前任务完成
线程池任务执行的顺序
最开始线程池中会有空线程在等待任务,当有任务出现就会安排线程去执行,如果没有空线程就会创建新的线程,当线程达到了核心线程数并且没有线程是空闲的 就开始往队列里面添加线程,如果队列中线程也达到饱和状态则继续创建新线程,直到线程数量达到最大线程数,开始进行拒绝策略
需要注意的是当任务队列已满且线程池的最大线程数尚未达到上限时,此时有新的线程出现,新线程将会被创建,并且会执行这个新的任务,而不是从队列中取出任务。
具体来说,当任务队列已满时,线程池会创建新的线程,并将新的任务分配给这个新线程来执行。这样可以避免将新任务放入已满的队列中,而是直接创建线程来处理新任务。
当线程池的核心线程数、任务队列容量和最大线程数都达到上限时,才会根据拒绝策略来处理新的任务
线程池拒绝策略
四种策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
线程池的默认拒绝策略为AbortPolicy,即丢弃任务并抛出RejectedExecutionException异常。
通过代码来验证这一点,现有如下代码:
public class ThreadPoolTest {
public static void main(String[] args) {
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(60);
ThreadFactory factory = r -> new Thread(r, "test-thread-pool");
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,
0L, TimeUnit.SECONDS, queue, factory);
while (true) {
executor.submit(() -> {
try {
System.out.println(queue.size());
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
设置了最大队列数为60,没有设置拒绝策略,运行代码则会抛出RejectedExecutionException异常
应用场景分析
AbortPolicy
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
A handler for rejected tasks that throws a {@code RejectedExecutionException}.
这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。
DiscardPolicy
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
A handler for rejected tasks that silently discards therejected task.
使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。
DiscardOldestPolicy
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
A handler for rejected tasks that discards the oldest unhandled request and then retries {@code execute}, unless the executor is shut down, in which case the task is discarded.
此拒绝策略,是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。
CallerRunsPolicy
ThreadPoolExecutor.CallerRunsPolicy:由主线程处理该任务
A handler for rejected tasks that runs the rejected task directly in the calling thread of the {@code execute} method, unless the executor has been shut down, in which case the task is discarded.
import java.util.concurrent.*;
public class Test {
public static void main(String[] args) {
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);
ThreadFactory factory = r -> new Thread(r, "test-thread-pool");
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5,
5,
0,
TimeUnit.MILLISECONDS,
queue, factory,
new ThreadPoolExecutor.CallerRunsPolicy()
);
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
try {
System.out.println(Thread.currentThread().getName() + ":执行任务");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
运行结果如下:
前三种都适用于并发量高的业务,但是第四种适用于业务重要的场景,不能够丢失数据