提到线程池,我们可以联想到常量池,程序池等等
“池”这种思想,本质上是为了提高程序的效率
一.线程池是什么
最初引入线程,就是因为线程太重了,频繁的创建销毁进程开销比较大。
随着业务上对于性能的要求越来越高,对应的,线程创建/销毁的频率越来越多。
此时线程创建/销毁开销变得越来越明显,无法忽略不计了。
线程池就是解决以上问题的常见办法。
线程池就是把线程以前从系统中申请好,放到一个地方。后面需要使用线程时,直接来这个地方来取,而不是从系统中从新申请。
线程用完后也是还回原来的地方,以便后续的使用。
为啥线程从线程池里取比从系统中申请更为高效?
原因涉及用户态与内核态的知识点(操作系统中的概念)
操作系统=操作系统内核+操作系统配套的应用程序。
操作系统内核负责完成一个操作系统的核心工作(管理)。
建设一个去银行办卡的场景。办卡需要携带身份证,假如滑稽老哥忘带身份证,柜台人员提供了两种解决方案:
1)自己去右下角自助服务打印机打印
2)由柜台人员帮你打印。
若是让柜台人员帮你打印,对方可能除了打印外,还有其他的事情要做(上厕所,喝水....),打印回来的时间不可控,就更慢。
若是自己去自助打印机打印,整个过程是流畅,可控的,效率更高。
从系统中创建线程,就相当于让柜台人员帮你打印~~这样的逻辑就是调用系统api,api由内核执行一系列逻辑完成这个过程
从线程池取线程,就相当于自己去自组打印机打印~~整个过程都是用户态代码,整个过程自己控制,时间可控,效率更高。
因此,通常认为纯用户态操作比经过内核操作效率更高
二.线程池标准库
Java标准库中提供了现成的线程池。
1.ThreadPoolExecutor
ThradPoolRxecutor类有4种构造方法
其中第4种构造方法涵盖了前3种,重点理解第四种
参数含义
第一个与第二个参数:
此线程中,可以支持线程扩容。
某个线程池中,初始情况下可能有M个线程。
实际使用中,发现线程M不太够用,就会自动增加M的个数
在Java标准库中,就把里面的线程分成两类:
1.核心线程(可以理解为最少有多少个线程)
2.非核心线程(可以理解为扩容过程中新增的线程)
核心线程数+非核心线程数的最大值=最大线程数
核心线程会始终存在于线程池内部。
非核心线程,繁忙时被创建出来,空闲时,则被释放。
第三个和第四个参数:
第二个和第三个参数要搭配着来使用,用于表示非核心线程允许存在的最大时间。
KeepAliveTime填数值,unit填数值的单位。
第五个参数:
线程池的工作过程是典型的“生产者消费者模型”
程序员使用时,通过形如“submit”这样的方法,把要执行的认为,设定到线程池里。
线程池内部的工作线程负责执行这些任务。
此处就有一个阻塞队列,“submit”就是把任务加到队列中,线程池干活时从队列取任务
此处的队列,让我们自行指定
1)队列的容量capacity
2)队列的类型
第六个参数:
工厂是指“工厂设计模式”,也是一种常见的设计模式~~
是一种在创建类的实例时使用的设计模式
由于构造方法有“坑”,通过工厂的设计模式来填坑。
ThreadFactory就是Thread的工厂类,通过这个类完成Thread的实例创建和初始化操作。
什么是构造方法的“坑”?
坑在构造方法的“重载”。
假如要创建一个描述点位置的类,我们可以用直角坐标系来描述点的位置,也可以用极坐标表示。
用代码表示,就有两种方法
但由于参数类型和数量相同,这两方法无法重载。
使用工厂设计模式,可以解决上诉问题。
通过使用静态方法完成对象的构造和初始化工作
此处创建对象的static方法称为“工厂方法”
用来放工厂方法的类,叫做“工厂类”
第七个参数(最重要,也最复杂):
当线程池的队列满了,还是要继续给这个队列添加任务,应该咋办?
应该要明确的拒绝。
Java标准库给出了4种不同的拒绝策略
队列满的情况无法避免,需结合实际情况,选择合适的拒绝策略。
2.Executors
ThreadPoolExecutor功能强大,使用起来很麻烦。
因此Java标准库对这个类进一步封装了以下
Executors提供了一些工厂方法,可以更便捷的构造出线程池
Executors提供了一些方法,都是对ThraedPoolExecutor进行封装
重点了解前两个
参数内设置了非常大的线程数,就可以对线程池不停的扩容。
把核心线程数和最大线程数设置为一样的值。(固定数量线程,不会自动扩容)
submit()
核心方法:指定一个任务到任务列表中。
service.submit()
创建一个线程池,线程池存储4个线程,线程共同执行100个任务,任务为打印i的值与线程名称
代码如下:
public class Demo1 {
public static void main(String[] args) {
ExecutorService service= Executors.newFixedThreadPool(4);
for (int i = 0; i < 100; i++) {
int id=i;
service.submit(()->{
Thread current=Thread.currentThread();
System.out.println(id+""+current.getName());
});
}
}
}
运行代码:
运行发现,虽然100个任务都执行完毕,程序却并没有结束。
这是因为,此处线程池创建出来的线程,默认都是“前台线程”(能阻止进程结束)。
虽然main线程结束了,但是这些线程池里的前台线程依然存在。
线程池提供了一个结束前台线程的方法
service.shutdown();
此方法可以将线程池里的所有方法都终止掉。
完整代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
ExecutorService service= Executors.newFixedThreadPool(4);
for (int i = 0; i < 100; i++) {
int id=i;
service.submit(()->{
Thread current=Thread.currentThread();
System.out.println(+id+""+current.getName());
});
}
// 不要立刻就终止,要等线程池里的线程把任务执行完
Thread.sleep(1000);
// 终止全部线程池线程
service.shutdown();
System.out.println("程序退出");
}
}
成功完成线程全部任务并退出。
使用线程池时,需指定线程个数,线程个数取多少合适呢。
网上有形形色色的答案,但在实际开发中,应通过“实验”的方法找到一个合适的线程池的个数。
三.实现线程池(简单的实现)
首先,自己实现一个简单的线程池
思路
1)创建一个工作队列(阻塞队列),用来存放任务。
2)构造方法创建线程,并依次取出并执行工作队列的任务。(利用queue.take()取出)
3)实现submit()功能,将任务放入工作队列(利用queue.put()将任务存放进工作队列中)
代码
代码如下
class MyThreadPool{
//n表示有多少个线程
private BlockingQueue<Runnable> queue=new ArrayBlockingQueue<>(1000);
public MyThreadPool(int n) {
for (int i = 0; i < n; i++) {
Thread t = new Thread(() -> {
while (true) { //循环取出工作列表的任务
try {
Runnable runnable = queue.take();
runnable.run(); //执行任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable); //将任务放入工作列表
}
}
测试
创建线程池,执行1000个任务,任务为打印任务序号和打印线程名字。
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool=new MyThreadPool(4);
for (int i = 0; i < 1000; i++) {
int id=i;
pool.submit(()->{
System.out.println("执行任务"+id+","+Thread.currentThread().getName() );
});
}
}
}
成功运行~~~~~~
完整代码:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
class MyThreadPool{
//n表示有多少个线程
private BlockingQueue<Runnable> queue=new ArrayBlockingQueue<>(1000);
public MyThreadPool(int n) {
for (int i = 0; i < n; i++) {
Thread t = new Thread(() -> {
while (true) { //循环取出工作列表的任务
try {
Runnable runnable = queue.take();
runnable.run(); //执行任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable); //将任务放入工作列表
}
}
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool=new MyThreadPool(4);
for (int i = 0; i < 1000; i++) {
int id=i;
pool.submit(()->{
System.out.println("执行任务"+id+","+Thread.currentThread().getName() );
});
}
}
}