前言
前面我们谈完了定时器,单例模式,阻塞队列等的操作并且做了模拟实现,今天我们再来说一说线程池的操作以及一些锁策略.
注:本章几乎均为理论篇,实践较少.
下面就让我们开始吧.
线程池
我们知道因为进程的频繁创建和销毁,带来的开销过大,我们无法接受,所以我们引入了更轻量级的线程,但是其实这里的线程频繁消耗的话,带来的开销也是我们无法接受的,所以我们又想了两个方案,线程池方案或者是更加轻量级的协程/纤程
纤程本质上是靠程序员在用户态进行调度,不是靠内核的调度器来操作,这节省了很多的开销
举个例子,如果一个进程中开一千个线程,电脑就卡死了,但是开一千个协程,其实是轻轻松松的
线程池的应用来说就是把线程提前创建好,用完了不要着急销毁,放进线程池里,以备下次使用,在这个过程中并没有频繁的创建和销毁线程,所以相对来说开销就小得多了,只是从线程池中取线程来使用,用完了还给线程池即可
tips:内核态和用户态之间的交互可以理解为,你去银行办事情,工作人员让你去去整一个身份证复印件,你可以让他帮你整,或者去自己整,他帮你整的话你无法掌控他什么时候帮你整,但是你自己整的话就能确定自己的时间安排
JVM提供的线程池(了解参数即可)
这里我们只要了解最后一个线程池的参数即可,因为几个方法的参数是重复的,最后一个的参数是最全的.
corePoolSize: 核心线程数 一个线程池里,最少有多少个线程
maximumPoolSize :最大线程数 一个线程池中,最多有多少个线程
keepAliveTime:线程空闲超过这个时间阈值,就会被销毁
unit: 时间单位,取分钟,秒,小时等等
workQueue: 和定时器一样,线程池也可以有很多任务,也可以设置为带有优先级的
threadFactory: 线程工厂,本质上是给new这个操作封装了一层,可能同名同参数的构造方法,这样构成不了重载,我们就想弥补一下这个缺陷,封装一层构造方法.
handler:拒绝策略(最重要的)
以下是四个Java标准库中提供的拒绝策略
解决的是在阻塞队列满了之后,你还想往里面添加任务,我们以何种方式拒绝
第一种:直接不干了,旧任务和新任务都不干了,直接抛一个异常
第二种:新的任务,由添加任务的线程自己执行
第三种:丢弃一个最老的任务来执行这个任务
第四种:直接丢弃最新的任务
.
但是这个类使用起来还是比较复杂的,所以标准库还提供了另一个版本来简化操作,Exectors
都是通过submit添加任务,只不过是构造方法有所差异
所以如果你想实现高度定制化的线程池就使用上面这个很多参数的,不然就可以使用Exectors即可
注:一个线程池中有多少线程不是根据网上说的cpu核心数-1等等这么简单,其实还是有很多细节之处的,需要我们通过测试来决定,因为有些线程是IO密集型,有些是cpu密集型的,根据情况而定
线程池的简单实现
使用一个列表来存放队列,使用一个阻塞队列来存放任务,其中只有一个submit方法来新建任务
class MyThreadPool{ private List<Thread> threadList = new ArrayList<>(); private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000); public MyThreadPool(int n){ for (int i = 0; i < n; i++) { Thread t = new Thread(()->{ //TODO while(true){ try { //队列为空则会阻塞 Runnable runnable = queue.take(); //取出任务直接执行就行 runnable.run(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); threadList.add(t); } } public void submit(Runnable runnable) throws InterruptedException { queue.put(runnable); } }