一、认识线程池
1、线程池是什么
虽然线程比进程轻量,但是频繁地创建销毁线程,仍然会有很大的开销,线程池就是用来优化这个问题的~
我们可以提前创建好一个拥有多个线程的线程池,执行任务的时候,就直接从线程池中取,不需要创建新的线程;执行完任务后,也不需要销毁线程,而是把它放回到线程池中~
Q:为什么从线程池中取线程要比创建新的线程更高效呢?
A:创建线程是在操作系统内核中完成的,涉及到用户态和内核态之间的相互切换的操作,而这些操作都是存在一定的开销。
但是从线程池中取线程只涉及到用户态操作,因此开销会更小~
2、标准库的线程池
通过Executors类来创建线程池,返回类型为ExecutorService
Executors本质上是ThreadPoolExecutor类的封装,由于ThreadPoolExecutor的构造方法比较复杂,参数比较多,于是Executors把ThreadPoolExecutor的构造方法封装到自己的一些静态方法中(工厂模式)。
Executors创建线程的几种方式:
方法名 | 说明 |
newFixedThreadPool | 创建一个固定数量线程的线程池 |
newCachedThreadPool | 创建一个线程数量动态增长的线程池 |
newSingleThreadExecutor | 创建只包含单个线程的线程池 |
newScheduledThreadPool | 设定延迟时间后执行命令,或者定期执行命令(相当于进阶版的定时器) |
创建好线程之后,可以通过submit方法来注册一个任务到线程池中:
二、模拟实现线程池
简单实现一个固定数量线程的线程池~
思路:
(1) 我们的线程池中要可以同时有N个线程,以及M个任务;
(2) 使用生产者消费者模型(阻塞队列)把M个任务分配给N个线程;
(3) 队列为空,每个线程都阻塞等待;
队列不为空,每个线程不停地取出并执行任务,直到队列为空。
public class MyThreadPool {
//创建一个阻塞队列用来存放任务
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
//在构造方法中创建n个线程
public MyThreadPool(int n){
for (int i = 0; i < n; i++) {
Thread t = new Thread(() -> {
while (true){
try {
//从队列中取出任务并执行
Runnable task = queue.take();
task.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//不要忘记启动线程
t.start();
}
}
public void submit(Runnable task) throws InterruptedException {
//把提交的任务放入到队列中
queue.put(task);
}
}