线程池
文章目录
一,前言
虽然线程给我们程序带来了更高的执行效率,但是线程不是创建的越多越好,那么线程创建的过多,会带来什么问题呢?
首先线程的创建和销毁都是很耗时很浪费性能的操作
(new三五个Thread还好,我需要一千个线程呢?)
线程之间频繁的进行上下文切换,增加系统的负载
为了解决上述问题,线程池诞生了,线程池的核心思想就是:线程复用。
也就是说线程用完后不销毁,放到池子里等着新任务的到来,反复利用N个线程来执行所有新老任务。这带来的开销只会是那N个线程的创建,而不是每来一个请求都带来一个线程的从生到死的过程。
二,线程池
概念
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。 线程池是一种线程复用的技术,可以有效地控制线程的数量,减少线程创建和销毁带来的开销,提高系统响应速度,并方便线程管理。(官方)
简单来说,线程池就是提前创建好一批线程,当有任务的时候,从池子中取出一个线程去执行该任务,执行结束后,再把线程放回池子中,以备循环使用。
三,参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
参数解释
corePoolSize:核心线程数
线程池在完成初始化之后,默认情况下,线程池中不会有任何线程,线程池会等有任务来的时候
再去创建线程。核心线程创建出来后即使超出了线程保持的存活时间配置也不会销毁,核心线程
只要创建就永驻了,就等着新任务进来进行处理。
maximumPoolSize:最大线程数
核心线程忙不过来且任务存储队列满了的情况下,还有新任务进来的话就会继续开辟线程,但是
也不是任意的开辟线程数量,线程数(包含核心线程)达到最大线程数后就不会产生新线程了,
就会执行拒绝策略。
keepAliveTime:线程保持的存活时间
如果线程池当前的线程数多于核心线程数,那么如果多余的线程空闲时间超过线程保持的存活时
间,那么这些多余的线程(超出核心线程数的那些线程)就会被回收。
unit:线程保持的存活时间单位
比如:TimeUnit.MILLISECONDS、TimeUnit.SECONDS
workQueue:任务存储队列
核心线程数满了后还有任务继续提交到线程池的话,就先进入任务存储队列。
workQueue通常情况下有如下选择:
LinkedBlockingQueue:无界队列,意味着无限制,其实是有限制,大小是int的最大值。也可以
自定义大小。
ArrayBlockingQueue:有界队列,可以自定义大小,到了阈值就开启新线程(不会超过最大线
程数)。
SynchronousQueue: Executors.newCachedThreadPool();默认使用的队列。
一般都采取无界队列,因为他也可以设置大小,可以取代有界队列。
threadFactory:当线程池需要新的线程时,会用threadFactory来生成新的线程
默认采用的是 DefaultThreadFactory ,主要负责创建线程。 newThread() 方法。创建出来的
线程都在同一个线程组且优先级也是一样的。
handler:拒绝策略,任务量超出线程池的配置限制或执行shutdown还在继续提交任务的话,会执行handler 的逻辑。
默认采用的是 AbortPolicy ,遇到上面的情况,线程池将直接采取直接拒绝策略,也就是直接抛
出异常。 RejectedExecutionException
四种内置的拒绝策略
1. AbortPolicy(默认):直接抛出RejectedExecutionException异常,阻止系统正常运行。
2. CallerRunsPolicy:由调用线程(提交任务的线程)执行被拒绝的任务。这样做可以降低新任务
的提交速度,但可能会影响整体性能。
3. DiscardPolicy:默默地丢弃被拒绝的任务,不做任何处理。
4. DiscardOldestPolicy:丢弃最早被放入队列的任务,然后尝试重新提交被拒绝的任务。
//自定义拒绝策略,实现RejectedExecutionHandler接口,并重写rejectedExecution方法来定义自
己的处理逻辑
四,线程池的实现原理
从图中我们可以看到完整的执行流程
- 线程提交到线程池
- 判断核心线程池是否已经达到设定的数量,如果没有达到,则直接创建线程执行任务
- 如果达到了,则放在队列中,等待执行
- 如果队列已经满了,则判断线程的数量是否已经达到设定的最大值,如果达到了,则直接执行拒绝策略
- 如果没有达到,则创建线程执行任务。
5.线程池的使用案例(自定义线程池)
import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建线程池
ExecutorService executorService = new ThreadPoolExecutor(
2, //两个核心线程数
4, //最大线程数
60, // 线程保持的存活时间
TimeUnit.SECONDS,//指定了 keepAliveTime 的单位为秒
new ArrayBlockingQueue<>(10),//最多可以在这个队列中排队 10 个任务
Executors.defaultThreadFactory(),// Java 提供的默认线程工厂来创建新线程
new ThreadPoolExecutor.AbortPolicy());//拒绝策略
// 提交任务
for (int i = 0; i < 10; i++) {
executorService.submit(() -> {//submit 方法:用于提交一个可执行的任务
//() -> { ... }:表示一个实现了 Runnable 接口的匿名类。大括号内的代码是你希 望在独立线程中执行的逻辑。
try {
System.out.println("执行任务开始 " + Thread.currentThread().getName());
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 关闭线程池
executorService.shutdown();
}
}
//输出结果
执行任务开始 pool-1-thread-1
执行任务开始 pool-1-thread-2
执行任务开始 pool-1-thread-2
执行任务开始 pool-1-thread-1
执行任务开始 pool-1-thread-2
执行任务开始 pool-1-thread-1
执行任务开始 pool-1-thread-2
执行任务开始 pool-1-thread-1
执行任务开始 pool-1-thread-1
执行任务开始 pool-1-thread-2
在输出中,pool-1-thread-1
和 pool-1-thread-2
中的数字分别代表以下含义:
- pool-1:这是线程池的名称。
1
表示这是第一个创建的线程池,如果有多个线程池,则会以递增的数字命名。 - thread-1 和 thread-2:这些表示线程在该线程池中的编号。它们是按照创建顺序递增的。
thread-1
是第一个线程,thread-2
是第二个线程,依此类推。
因此,整个字符串表示该线程池中的具体线程,帮助我们识别和调试哪个线程在执行哪个任务。
6.使用Executors 创建常见的功能线程池
Executors为我们封装好了 4 种常见的功能线程池如下:
- 定长线程(固定大小):FixedThreadPool
- 定时线程:ScheduledThreadPool
- 可缓存线程池:CachedThreadPool
- 单线程化线程池:SingleThreadExecutor
1.固定大小线程池
核心线程数和最大线程数是一样的,所以称之为固定线程数。
其他参数配置默认为:永不超时(0ms),无界队列( LinkedBlockingQueue )、默认线程工厂
( DefaultThreadFactory )、直接拒绝策略( AbortPolicy )。
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
for (int i = 0; i < 10; i++) {
// 从结果中可以发现线程name永远都是两个。不会有第三个。
executorService.execute(() -
> System.out.println(Thread.currentThread().getName()));
}
}
}
2.定时线程
核心线程数手动传进来,最大线程数是Integer.MAX_VALUE,最大线程数是内部默认的,不可更改。
其他参数配置默认为:永不超时(0ns),带延迟功能的队列( DelayedWorkQueue )、默认线程工厂
( DefaultThreadFactory )、直接拒绝策略( AbortPolicy )。
public class ThreadPoolTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newSchedul
edThreadPool(2);
// 五秒一次
scheduledExecutorService.schedule(() -> System.out.println(Thread.currentThread().getName()), 5, TimeUnit.SECONDS);
// 首次五秒后执行,其次每隔1s执行一次
scheduledExecutorService.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName()), 5, 1, TimeUnit.SECONDS);
}
3.可缓存线程池
他的功能是来个任务我就开辟个线程去处理,不会进入队列, SynchronousQueue 队列也不带存储元素
的功能。那这意味着来一亿个请求就会开辟一亿个线程去处理,keepAliveTime为60S,意味着线程空
闲时间超过60S就会被杀死;这就叫带缓存功能的线程池。
核心线程数是0,最大线程数是int的最大值,内部默认的,不可更改。
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
// 从结果中可以发现线程name有10个。也就是有几个任务就会开辟几个线程。
executorService.execute(() -
> System.out.println(Thread.currentThread().getName()));
}
}
}
4.单线程化线程池
核心线程数和最大线程数是1,内部默认的,不可更改,所以称之为单线程数的线程池。
类似于 Executors.newFixedThreadPool(1);
其他参数配置默认为:永不超时(0ms),无界队列( LinkedBlockingQueue )、默认线程工厂
( DefaultThreadFactory )、直接拒绝策略( AbortPolicy )。
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
// 从结果中可以发现线程name永远都是pool-1-thread-1。不会有第二个出现。
executorService.execute(() -
> System.out.println(Thread.currentThread().getName()));
}
}
}