前面我们了解了多个任务可以通过创建多个线程去处理,达到节约时间的效果,但是每一次的线程创建和销毁也是会消耗计算机资源的,那么我们是否可以将线程进阶一下,让消耗计算机的资源尽可能缩小呢?线程池可以达到此效果,今天我们对 《线程池》 一探究竟!
1. 何为线程池?
线程池(Thread Pool)是一种并发编程的技术,用于在应用程序启动时创建一定数量的线程,并将它们保存在线程池中,以便于任务的执行和线程的重用。当有新任务到来时,线程池会分配一个空闲线程来执行该任务,任务完成后,线程不会被销毁,而是返回线程池中,等待执行下一个任务。
2. 为什么要用线程池?
为什么要用线程池,那就得从线程池的优点来说了
优点:
- 重用线程:避免了频繁创建和销毁线程的开销,提高了线程的利用率。
- 控制并发度:可以限制并发执行的线程数量,防止系统过载。
- 提供线程管理和监控:方便开发人员进行线程的管理和调试。
- 提供任务队列:实现任务的缓冲和调度,提升系统响应速度。
3. 为什么使用线程池可以提升效率?
由于线程的频繁创建和销毁会影响计算机效率,所以我们可以减少线程的频繁创建和销毁来提高计算机效率,因为线程池可以实现这种效果,所以可以提升效率,因为线程池做的是少量创建,少量销毁,进而做到了提升效率的功能
线程池最⼤的好处就是减少每次启动、销毁线程的损耗。
从另一种方面来说,计算机分为 内核态(操作系统层面) 和 用户态JVM层面(应用程序层),创建和销毁线程是内核态的操作
4. 线程池怎么用?
Java当中JDK给我们提供了一组针对不同使用场景的线程池实例,如下:
//1.用来处理大量短时间工作任务的线程池,如果池中没有可用的线程将创建新的线程,如果线程空闲60秒将回收并移除缓存
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//2.创建一个操作无界队列且固定大小线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//3.创建一个操作无界队列且只有一个工作线程的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//4.创建一个单线程执行器,可以在给定时间后执行或定期执行
ScheduledExecutorService singleThreadScheduledExecutor=Executors.newSingleThreadScheduledExecutor();
//5.创建一个指定大小的线程池,可以在给定时间后执行或定期执行
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
//6.创建一个指定大少(不传入参数,为当煎机器CPU核心数)的线程池,并行地处理任务
Executors.newWorkStealingPool();
说明:
- 使用Executors.newFixedThreadPool(3)能创建出固定包含3个线程的线程池.
- newCachedThreadPool:创建线程数目动态增长的线程池.
- newFixedThreadPool:创建固定线程数的线程池 ,返回值类型为ExecutorService.
- newSingleThreadExecutor: 创建只包含单个线程的线程池.
- newSingleScheduledThreadPool:创建一个单线程执行器,设定延迟时间后执行命令,或者定期执行命令,是进阶版的Timer.
- newScheduledThreadPool:创建一个指定大小的线程池,设定延迟时间后执行命令,或者定期执行命令.是进阶版的Timer.
- 无界:任务队列的容量 没有限制称为无界队列
- Executors本质上是ThreadPoolExecutor类的封装.
5. 自我实现一个线程池
思路:
- 首先要描述任务
通过Runnable描述任务
- 通过一个阻塞队列管理任务
//阻塞队列组织任务,最多处理100个任务
public static BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(100);
- 提供一个方法向线程池提交任务
//添加任务到阻塞队列当中
public void submit(Runnable runnable) throws InterruptedException {
if(runnable==null){
throw new IllegalAccessError("任务为空");
}
queue.put(runnable);
}
- 创建多个线程,不停的扫描队列中的任务
//threadNum为初始化时,给定的线程池中含有线程的个数
public MyThreadPool(int threadNum) {
if(threadNum<=0){
throw new RuntimeException("线程数量必须大于0");
}
for (int i = 0; i < threadNum; i++) {
Thread thread=new Thread(()->{
//不停的扫描线程
while (true) {
try {
//从线程中取出任务
Runnable runnable = queue.take();
//执行任务
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//启动线程
thread.start();
}
}
- 测试我们的线程池是否成功
public class Test {
public static void main(String[] args) throws InterruptedException {
//创建线程数量为3的线程池
MyThreadPool myThreadPool=new MyThreadPool(3);
//向线程池提交10个任务
for (int i = 0; i < 10; i++) {
int taskId=i+1;
myThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("执行任务"+taskId+", "+Thread.currentThread().getName());
}
});
}
}
}
- 完整的自我线程池如下:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class MyThreadPool {
//阻塞队列组织任务,最多处理100个任务
public static BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(100);
//添加任务到阻塞队列当中
public void submit(Runnable runnable) throws InterruptedException {
if(runnable==null){
throw new IllegalAccessError("任务为空");
}
queue.put(runnable);
}
public MyThreadPool(int threadNum) {
if(threadNum<=0){
throw new RuntimeException("线程数量必须大于0");
}
for (int i = 0; i < threadNum; i++) {
Thread thread=new Thread(()->{
//不停的扫描线程
while (true) {
try {
//从线程中取出任务
Runnable runnable = queue.take();
//执行任务
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//启动线程
thread.start();
}
}
}
6. 创建系统自带的线程池
执行流程:
- 添加任务,核心线程从队列中取任务去执行
- 核心线程都在工作时,再添加的任务会进入到阻塞队列
- 阻塞队列满了后,会创建临时线程
- 阻塞队列满了并且临时线程达到最大,执行拒绝策略
一次性创建最大的线程数(根据机器的配置,比如CPU核数)
图解:
说明:
- corePoolSize: 核心线程的数量.(类似于正式员⼯,⼀旦录⽤,永不辞退)
- maximumPoolSize:线程池中最大的线程数=核心线程+临时线程的数目.(类似于临时⼯:⼀段时间不⼲活,就被辞退)
- keepAliveTime:临时线程存活的时间
- unit:临时线程存活的时间单位,是秒,分钟,还是其他值
- workQueue:组织(保存)任务的队列
- threadFactory: 创建线程的工厂,参与具体的创建线程⼯作.通过不同线程工厂创建出的线程相当于对一些属性进行了不同的初始化设置
- RejectedExecutionHandler: 拒绝策略,如果任务量超出线程池的负荷了接下来怎么处理
接下来我们探讨一下这四种拒绝策略:
1. AbortPolicy(): 超过负荷,直接抛出异常.
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TestAbortPolicy {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor reject1 =new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 100; i++) {
int taskId = i + 1;
reject1.submit(() -> {
System.out.println("执行任务:" + taskId + ", " + Thread.currentThread().getName());
});
}
}
}
举个例子: 现在我在和我的女友玩游戏,老师让我帮他做一项报告,我直接拒绝了,并抛出异常,我不做!!
2. CallerRunsPolicy():返回给调⽤者负责处理多出来的任务.
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TestCallerRunsPolicy {
public static void main(String[] args) {
ThreadPoolExecutor reject2=new ThreadPoolExecutor(3,
5,
1,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 100; i++) {
int taskId = i + 1;
reject2.submit(()->{
System.out.println("执行任务:" + taskId + ", " + Thread.currentThread().getName());
});
}
}
}
举个例子: 现在我在和我的女友玩游戏,老师让我帮他做一项报告,我直接拒绝了,老师就自己去做了,保证了任务至少完成了。
3. DiscardOldestPolicy():丢弃队列中最⽼的任务.
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TestDiscardOldestPolicy {
public static void main(String[] args) {
ThreadPoolExecutor reject2=new ThreadPoolExecutor(3,
5,
1,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i = 0; i < 100; i++) {
int taskId = i + 1;
reject2.submit(()->{
System.out.println("执行任务:" + taskId + ", " + Thread.currentThread().getName());
});
}
}
}
举个例子: 现在我在和我的女友玩游戏,老师让我帮他做一项报告,我直接退出游戏(不处理最老的任务),去完成老师交给我的任务。
4. DiscardPolicy():丢弃新来的任务.
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TestDiscardPolicy {
public static void main(String[] args) {
ThreadPoolExecutor reject2=new ThreadPoolExecutor(3,
5,
1,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
new ThreadPoolExecutor.DiscardPolicy());
for (int i = 0; i < 100; i++) {
int taskId = i + 1;
reject2.submit(()->{
System.out.println("执行任务:" + taskId + ", " + Thread.currentThread().getName());
});
}
}
}
举个例子: 现在我在和我的女友玩游戏,老师让我帮他做一项报告,我直接拒绝了,老师也不做,日后可能忘记了该任务,再也找不回来。
总结: 在实际的开发过程中,我们要根据实际的业务场景选择不同的拒绝策略!!!
以上就是线程池的基本知识了,希望对你有帮助,谢谢!!