文章目录
🌷 线程池是什么
虽然创建线程 / 销毁线程 的开销
想象这么一个场景:
在学校附近新开了一家快递店,老板很精明,想到一个与众不同的办法来经营。店里没有雇人, 而是每次有业务来了,就现场找一名同学过来把快递送了,然后解雇同学。这个类比我们平时来一个任务,起一个线程进行处理的模式。
很快老板发现问题来了,每次招聘 + 解雇同学的成本还是非常高的。老板还是很善于变通的,知道了为什么大家都要雇人了,所以指定了一个指标,公司业务人员会扩张到 3 个人,但还是随着业务逐步雇人。于是再有业务来了,老板就看,如果现在公司还没 3 个人,就雇一个人去送快递,否则只是把业务放到一个本本上,等着 3 个快递人员空闲的时候去处理。这个就是我们要带出的线程池的模式。
线程池最大的好处就是减少每次启动、销毁线程的损耗
🌷 标准库中的线程池
- 使用
Executors.newFixedThreadPool(10)
能创建出固定包含 10 个线程的线程池. - 返回值类型为
ExecutorService
- 通过
ExecutorService.submit
可以注册一个任务到线程池中.
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
Executors
创建线程池的几种方式 :
newFixedThreadPool
: 创建固定线程数的线程池newCachedThreadPool
: 创建线程数目动态增长的线程池.newSingleThreadExecutor
: 创建只包含单个线程的线程池.newScheduledThreadPool
: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的Timer
.
Executors
本质上是 ThreadPoolExecutor
类的封装
ThreadPoolExecutor
提供了更多的可选参数, 可以进一步细化线程池行为的设定.
六种创建线程池的方式:
public class Demo01_Threadpool {
public static void main(String[] args) {
// 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();
}
}
🌷 简单使用线程池
public class Demo03_ThreadPoolUse {
public static void main(String[] args) throws InterruptedException {
// 创建一个线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3);
// 提交任务到线程池
for (int i = 0; i < 10; i++) {
int taskId = i;
threadPool.submit(() -> {
System.out.println("我是任务 " + taskId + ", " + Thread.currentThread().getName());
});
}
// 模拟等待任务
TimeUnit.SECONDS.sleep(5);
System.out.println("第二阶段开始");
// 提交任务到线程池
for (int i = 10; i < 20; i++) {
int taskId = i;
threadPool.submit(() -> {
System.out.println("我是任务 " + taskId + ", " + Thread.currentThread().getName());
});
}
}
}
🌷 实现线程池
核心操作为 submit
, 将任务加入线程池中
- 使用
Worker
类描述一个工作线程. 使用Runnable
描述一个任务. - 使用一个
BlockingQueue
组织所有的任务 - 每个
worker
线程要做的事情: 不停的从BlockingQueue
中取任务并执行. - 指定一下线程池中的最大线程数
maxWorkerCount
; 当当前线程数超过这个最大值时, 就不再新增线程了.
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class MyThreadPool {
// 1. 定义一个阻塞队列来保存我们的任务
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(3);
// 2. 对外提供一个方法,用来往队列中提交任务
public void submit (Runnable task) throws InterruptedException {
queue.put(task);
}
// 3. 构造方法
public MyThreadPool (int capacity) {
if (capacity <= 0) {
throw new RuntimeException("线程数量不能小于0.");
}
// 完成线程的创建,扫描队列,取出任务并执行
for (int i = 0; i < capacity; i++) {
// 创建线程
Thread thread = new Thread(() -> {
while (true) {
try {
// 取出任务(扫描队列的过程)
Runnable take = queue.take();
// 执行任务
take.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 启动线程
thread.start();
}
}
}
import java.util.concurrent.TimeUnit;
public class Demo04_MyThreadPool {
public static void main(String[] args) throws InterruptedException {
// 创建自定义的线程池
MyThreadPool threadPool = new MyThreadPool(3);
// 往线程池中提交任务
for (int i = 0; i < 10; i++) {
int taskId = i;
threadPool.submit(() -> {
System.out.println("我是任务 " + taskId + ", " + Thread.currentThread().getName());
});
}
// 模拟等待任务
TimeUnit.SECONDS.sleep(3);
System.out.println("第二阶段开始");
// 提交任务到线程池
for (int i = 10; i < 20; i++) {
int taskId = i;
threadPool.submit(() -> {
System.out.println("我是任务 " + taskId + ", " + Thread.currentThread().getName());
});
}
}
}
🌷 一些面试题:
⭐️ 如何自定义一个线程池,以及创建池的构造方法参数的含义?
- 通过创建一个
ThreadPoolExecutor
int corePoolSize
:核心线程数,创建线程池时包含的最小线程数int maximumPoolSize
:最大线程数,核心数不够用时,允许系统可以创建的最多线程long keepAliveTime
:临时线程空闲的时长TimeUnit unit
:空闲的时间单位,和keepAliveTime
一起使用BlockingQueue<Runnable> workQueue
:用来保存任务的阻塞队列ThreadFactory threadFactory
:线程工厂,如何去创建线程RejectedExecutionHandler handler
:拒绝策略,触发的机制:当线程池处理不了这么多任务时
⭐️ 有时候会问,创建线程池时,核心线程数多少比较合适?
- 这个没有一个准确的答案,要根据业务场景余计算机配置来决定
- 对于计算密集型(CPU执行)的程序,那么线程数可以适当调大;对于IO密集型的程序,取决于磁盘的读取效率,那么线程数过大也不会提高程序效率
- 还要考虑CPU得核心数量是多少
- 最终还是要通过测试对比,来确定一个合适的线程数
⭐️描述线程池工作原理?
- 当任务添加到线程池时,会先判断任务数是否大于核心任务数
- 如果不大于直接执行,否则假如阻塞队列
- 当阻塞队列满了之后,会按照指定的线程最大数创建临时线程(一次性添加)
- 当阻塞队列满了,临时线程也满了,就会执行拒绝策略
- 当任务减少,临时线程达到空间时长时,会被回收
- 拒绝策略