线程池
什么是线程池,是一种池化思想,这种思想在我们java编程中使用的非常普遍。类似的池化思想还有哪些呢?以下三个就是三个典型的池化思想 ,池化思想是一种优化程序的重要方式 。可以提高资源利用率
。
- 线程池
- 字符串常量池
- 数据库连接池
线程池顾名思义就是对线程的一种优化,如果说我们不使用线程池,那么我们对线程的操作呢就是
第一步:手动创建线程对象
第二歩:去执行任务,因为线程肯定是需要去执行一个任务
第三步:执行完毕,释放线程对象
这样的话,它的资源利用率就很低,就相当于来一个任务就创建一个对象去执行,执行完了之后再把这个对象给他扔掉。然后再来下一个任务,我又去创建一个新的对象去执行,执行完成之后又给扔掉。效率非常低 。
就好比打电话,买个手机打完电话后把手机扔了,然后当要打下一个电话的时候又买个手机,打完之后又把手机扔了。这样肯定是很浪费钱的一种方式。
正确的做发是买一部手机然后重复去利用
所以线程池也是一样的道理,不要说来一个任务创建一个线程对象,执行完了之后给他扔掉,我们应该让这个线程对象可以重复去利用,可以去执行多个任务。那这种方式怎么去实现呢,我们就可以通过线程池这样一个机制来完成
。
基本思想
基本思想就是先预先创建若干个线程对象,然后放到缓冲池里边。
比如下图,预先创建三个线程对象。然后有两个任务准备执行。
当有一个任务来了的时候,从线程池里面拿出来一个线程对象去执行任务,下图来两个任务,所以从线程池拿两个线程对象去执行这两个任务
当任务执行完毕,会将线程对象放回线程池
当我们同时有四个任务呢,其中三个各占据一个线程对象。第四个任务会进入到等待队列
,等待着其他任务对象执行完毕将线程对象放回线程池。
如果任务执行完毕,释放了线程对象,那么等待队列中的任务将会去被释放的线程对象用来执行自己的任务
这样的话,就可以做到一个资源重复利用了。这就是线程池的一个基本思想。
线程池的优点
- 提高线程的利用率
- 提高程序的响应速度
- 便于统一管理线程对象
- 可以控制最大的并发数
我们可以通过设置线程池的参数来控制线程池的容量,进而控制系统的最大并发量
应急处理预案
设想,如果线程池的线程对象都在执行任务了,等待队列也已经排满了。此时还有新的任务进来我们应该怎么办?
解决: 这个时候,线程池就会
创建新的线程对象
用来执行等待队列外面的任务
但是,我们不可能无限的去创建线程对象,因为我们的线程池容量是有限的它会有一个最大的线程数,最大线程数指的就是我们当前线程池中能够容纳的最大线程数量的个数
假如最大就是五个,这个时候线程池的线程对象也有五个了。但是此时又来一个新任务了,这个时候怎么办呢?只能拒绝掉了。
- 要么直接抛异常
- 要么让其他线程去执行 。
总之呢,线程池是处理不了了
拒绝策略
什么是拒绝策略?就是线程池已经到了最大数量了,并且全部线程对象都已经在执行任务了,等待队列也已经满了。但是又有新的任务进来的应对方式。
java中有以下四种应对策略。
任务拒绝策略 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy() | 默认策略:丢弃任务并抛出RejectedExecutionException异常 |
ThreadPoolExecutor.DiscardPolicy() | 丢弃任务,但是不抛出异常(这是不推荐的做法 ) |
ThreadPoolExecutor.DiscardOldestPolicy() | 抛弃队列中等待最久的任务(也就是目前排在第一位的任务 ,然后把当前任务追加 到等待队列中) |
ThreadPoolExecutor.CallerRunsPolicy() | 调用任务的run()方法绕过线程池直接执行任务 |
代码简单实现
JUC并发工具包已经给我们提供了ThreadPoolExecutor
类,我们通过这个类就可以直接创建线程池对象。
- param一,核心线程数,也就是线程池预先创建的常备的线程数量
- param二,最大线程数,当线程对象不够用了,继续创建线程对象的数量上限
- param三,当遇到高并发时,线程池新创建的线程对象的存活时间。1秒。也就是1秒钟没有接到任务,那么后面常见的线程对象就销毁
- param四,时间单位,我们这里设置为秒
- param五,等待队列,我们这里设置的为三个
- param六,拒绝策略,我们这里抛个异常
package com.xzmadmin;
import java.util.concurrent.*;
/**
* @author Administrator
* @version 1.0
* @date 2024-10-28 下午5:33
*/
public class Test {
public static void main(String[] args) {
ExecutorService executor = new ThreadPoolExecutor(
3, // 核心线程数,也就是线程池预先创建的常备的线程数量
5, // 最大线程数,当线程对象不够用了,继续创建线程对象的数量上限
1L, // 当遇到高并发时,线程池新创建的线程对象的存活时间。1秒。也就是1秒钟没有接到任务,那么后面常见的线程对象就销毁
TimeUnit.SECONDS, // 时间单位,我们这里设置为秒
new ArrayBlockingQueue<>(3), // 等待队列,我们这里设置的为三个
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()); // 拒绝策略,我们这里抛个异常
for (int i = 0; i < 9; i++) {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + "这个线程===> 在办理业务");
});
}
}
}
运行结果:
pool-1-thread-3这个线程===> 在办理业务
pool-1-thread-1这个线程===> 在办理业务
pool-1-thread-2这个线程===> 在办理业务
pool-1-thread-1这个线程===> 在办理业务
pool-1-thread-3这个线程===> 在办理业务
当我们将循环次数改成9之后,就会报下面这个错误。因为最大线程数为5,等待队列数为3,5+3=8。当第九个任务来了之后,线程池已经没有多余的线程对象去处理了,已经处理不了了。就会被拒绝。我们这里就让他抛个异常