1. 什么是线程池
所谓的线程池就是提前把线程准备好,创建线程不是直接从系统申请,而是从池子里拿~
线程不用了,就还给池子~
池的目的是为了提高效率,线程的创建,虽然比进程轻量,但是在频繁创建的情况下,开销也是不可忽略的!
那么问题来了,为什么在池子里面拿线程就要比系统创建线程更高效呢?
- 从池子拿线程,纯粹的用户态操作
- 从系统创建线程,涉及到用户态和内核态之间的切换,真正的创建是要在内核态完成的~
下面简单介绍下用户态和内核态:
一个操作系统 = 内核 + 配套的应用程序
所谓的内核,就是操作系统最核心的功能模块集合,硬件管理、各种驱动、进程管理、内存管理、文件系统…
而内核就需要给应用层序提供给支持~
举个最简单的例子:
比如 print(“hello world”),应用程序就需要调用系统内核,告诉内核,要进行一个打印操作,内核再通过驱动程序,操作显示器,完成上述功能~
应用程序同一时刻,可以有很多个,而内核只有一个,所有有些时候,处理并不能那么及时~
总结:纯用户态操作时间是可控的,涉及到内核态操作,时间是不可控的
2. 标准库中的线程池
首先简单介绍下工厂模式:
所谓的工厂模式,就是用来填补构造方法的坑,我们都知道如果要提供多种构造对象的方式,就得基于重载!
可以举个例子:描述平面中点,可以用直角坐标系来表示,也可以用极坐标系来表示
// 此处写的伪代码
class Point{
public Point(double x ,double y){
//....
}
public Point(double r, doubule a){
//....
}
}
上述代码肯定是不可以的!!无法构成重载,此时就需要构造一个工厂类
class PointBuilder{
public static Point makePointByXY(double x,double y){
//....
}
public static Point makePointByRA(double r,double a){
//...
}
}
这样问题就迎刃而解了!这就是工程模式!
Java中标准库提供的线程池使用
public class Demo26 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(10); // 固定10个
// ExecutorService pool = Executors.newCachedThreadPool(); 按需创建
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
}
}
下面我们来分析下这段代码:
3. ThreadPoolExecutor类
可以点击 JAVA官方文档 查看该类的介绍
- int corePoolSize :核心线程数
- int maximumPoolSize:最大线程数
- long keepAliveTime:临时线程存活的时间
- TimeUnit unit:存活时间的单位
- BlockingQueue <Runnable > workQueue : 阻塞队列,管理任务
- ThreadFactory threadFactory:线程工厂,创建线程的辅助的类
- RejectedExecutionHandler handler:线程池的拒绝策略,当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略
3.1 标准库提供的四种拒绝策略(重点掌握)
- 第一个 ThreadPoolExecutor.AbortPolicy : 如果满了,继续添加任务,添加操作直接抛出异常
- 第二个 ThreadPoolExecutor.CallerRunsPolicy:添加的线程自己执行任务
- 第三个 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
- 第四个 ThreadPoolExecutor.DiscardPolicy:丢弃任务,不抛异常
4. 自己实现一个线程池
class MyThreadPool{
// 阻塞队列用来存任务
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
// 此处实现一个固定线程数的线程池
public MyThreadPool(int n){
for (int i = 0; i < 10; i++) {
Thread t = new Thread(()->{
while (true){
// 此处需要让线程内部有个 while 循环,不停的取任务
try {
Runnable runnable = queue.take();
runnable.run();
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
}
}
}
public class Demo20 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool = new MyThreadPool(10);
for (int i = 0; i < 1000; i++) {
int number = i;
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello " + number);
}
});
}
}
}
从结果可以看出,线程池中任务执行的顺序和添加顺序不一定相同的!非常正常,这十个线程是无序调度的~
在上述代码中有两个小知识点:
在当前代码中,搞了10个线程的线程池,在实际开发中,一个线程池的线程数量,设置成多少,才合适呢?
不同的程序,线程做的活不一样
- CPU 密集型任务,做一些计算工作,要在CPU上运行
- IO密集型,主要是等待IO操作(等待读写硬盘,读写网卡),这些不咋吃CPU
所以极端情况下,如果你的线程全是使用CPU,线程数就不应该超过CPU核心数
如果你的线程全是IO操作,那线程数可以设置很多,可以远远超过CPU核心数
实践中,还是需要通过测试来确定~ 取一个执行效率OK,并且占用资源也OK的数量~