1.为什么要使用线程池?2.线程池的基本原理是什么?3.怎么学习线程池?
线程池使用了池化技术。池化技术用于我们常见的:数据库连接池,jvm中的常量池,对象池,线程池等等
池化技术:通俗的意思就是将一些我们经常用的资源预先放入一个池子中,后面我们需要使用资源的时候不需要再次创建,使用完毕之后也不需要销毁资源,只需要重新放回到池子中。因为很多大型并且常用的资源创建和销毁都是极具消耗内存和CPU的。
线程池优点:
- 线程池解决了线程的创建销毁的资源浪费的问题 。
- 便于管理线程。
- 提高线程的响应速度。
了解线程池的三大方法,七大参数,四种拒绝策略
三大方法
三大方法指的是创建线程池的三种方式(不推荐使用,因为容易导致oom。
我们开放一般会使用ThreadPoolExecutor创建线程池)。
- Executors.newSingleThreadExecutor():使用Executors创建一个线程池,线程池中只有一个线程(结果看下图:SingleThreadExecutor.png)
- Executors.newCachedThreadPool(): 使用Executors创建一个线程池,遇强则强,遇弱则弱。一个弹性的线程池,jvm会根据提交的任务数自动改变池中的线程数量(结果看下图:CachedThreadPool.png)
- Executors.newFixedThreadPool(int nThreads):使用Executors创建一个固定大小的线程池,参数是线程个数。(结果看下图:FixedThreadPool.png)
/**
* @author 小鱼
* @version 1.0
* @date 2022/12/3 11:39 上午
* 三大方法:
* 1.Executors.newSingleThreadExecutor()
* 使用Executors创建一个线程池,线程池中只有一个线程
* 2.Executors.newCachedThreadPool()
* 使用Executors创建一个线程池,遇强则强,遇弱则弱。一个弹性的线程池,会根据提交的任务数自动改变池中的线程数量
* 3.Executors.newFixedThreadPool(int nThreads)
* 使用Executors创建一个固定大小的线程池,参数是线程个数。
*/
public class Demo01 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// ExecutorService executorService = Executors.newCachedThreadPool();
// ExecutorService executorService = Executors.newFixedThreadPool(5);
try {
for (int i = 0; i < 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + " OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
七大参数
七大参数说的是ThreadPoolExecutor类中的七个参数,重点:核心线程数,拒绝策略
。
下面也会通过一个银行办理业务的列子详细的介绍这七个参数作用。
上面的三大方法中创建线程池其实内部实现还是调用了ThreadPoolExecutor类。
比如:newFixedThreadPool(int nThreads)方法。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
下面看一下ThreadPoolExecutor中怎么创建线程池的,并且7个参数有什么含义。
参数名 | 含义 |
---|---|
corePoolSize | 池中核心线程数量,也可以理解为保留的数量 |
maximumPoolSize | 池中线程最大的数量 |
keepAliveTime | 当任务超过了核心线程数量,多久超时了之后,开启池中其他空闲线程 |
unit | 超时单位 |
workQueue | 阻塞队列,当提交任务超过核心线程数量时,任务就会放入阻塞队列中 |
threadFactory | 创建线程池,一般使用默认的 |
handler | 拒绝策略,任务数量超过了池子最大处理范围 ,就会通过拒绝策略进行拒绝任务 |
- 最大处理范围:最大线程数量+最大阻塞队列长度
public class Demo02 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = null;
try {
threadPoolExecutor = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
for (int i = 1; i <= 8; i++) {
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName() + " OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPoolExecutor.shutdown();
}
}
}
四大拒绝策略
当任务数量超过线程池中最大处理范围,就会由线程池的拒绝策略处理任务
拒绝策略名称 | 具体含义 |
---|---|
AbortPolicy() | 多出的任务直接拒绝处理导致丢失任务),直接抛出异常:java.util.concurrent.RejectedExecutionException |
CallerRunsPolicy() | 多出来的任务,哪里来的回哪里去,线程池也不处理,不会抛出异常(见图:CallerRunsPolicy.png) |
DiscardPolicy() | 多出的任务直接拒绝处理,不抛出异常(导致丢失任务)。 |
DiscardOldestPolicy() | 多出的任务池子会去尝试的竞争最早处理任务的线程A,如果最早线程A还在处理任务,就直接拒绝任务。如果线程A没有处理任务,那么线程A会继续执行未处理的任务。不抛出异常。 |
银行业务办理列子
下面我们通过一个银行排队处理业务的列子深入理解一下这七大参数
-
场景:生活中,人们经常去银行办理业务,常见的是银行总共有5个窗口(最大线程数量),3个休息等待区(阻塞队列),一直开放的窗口有2个(核心线程数量),当人比较多的时候,休息区也满了还有人要办理业务的时候另外3个也会开放(任务等待超时了,空闲的线程去处理任务)。人员太多了,业务员处理到加班也处理不完,银行保安可能就告诉后面的人不用排队了,或者直接关门(拒绝策略)
怎么设置核心和最大的线程数
1.CPU型:cpu有几个线程就设置几个(Runtime.getRuntime().availableProcessors())
2.IO密集型:开放人员知道自己系统有多少个大IO操作任务,然后一般都是根据这个数量*2