作者:bravo1988
链接:https://www.zhihu.com/question/558457760/answer/2708771354
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
要了解线程池,必须先了解线程。
线程的简单概念
在Java中需要区分“线程”的两个不同维度的概念:
- thread
- Thread Java类/对象
thread
第一个thread,指的是实际意义上的“线程”,通常定义是:CPU的一条执行路径。线程(英语:thread)是cpu进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
作为非科班,我往往会望文生义,线程、线程,那么就应该像一条线,串联起我们的程序。虽然说不上错,但其实不够准确。我现在对线程的理解是,线程就是CPU的一条执行路径。
什么叫 CPU的一条执行路径呢?以单核CPU的计算为例,CPU在同一时刻实际上只能执行一个程序。那么为什么单核计算机上却可以同时听歌、敲代码呢?
这里就涉及到一个所谓的“时钟中断”和“时间分片”的概念。
CPU就像一头驴,一刻不停地在工作,而晶振片就像一把苍蝇拍,按照一定频率抽打CPU。
把左右两边的水龙头看做两个程序,也可以看做我们项目里的method1和method2。CPU在同一时刻只能执行其中一方,但通过晶振片,可以把CPU的执行时间打散成一个个小片段,叫做 时间分片,在每个时间段执行的代码路径就像上图的折线,一会儿执行左边的,一会儿执行右边的。
Thread类/对象
我们作为人类自然是无法直接拿着苍蝇拍打CPU的,也无法直接操作晶振片,我们需要另一层抽象:操作系统。操作系统负责调度每一段时间片,在不同的时间片去处理不同的程序,也就产生了多线程。
现在大概理解了操作系统和CPU层面的线程,那么JVM中的线程是什么概念呢?两者有什么关联吗?
首先要明白,JAVA作为一门编程语言,是没有产生线程的能力的。语言只能产生命令,却不能对现实产生实际的作用,它最终还是要依赖于实际媒介。就好比我问你,要不要吹空调。如果你说要,那我会找到遥控器打开空调,却不是自己吹出冷风。
所以JVM的线程是从操作系统借的。
Java中有个Thread类,可以得到Thread对象,通过Thread#start(),可以让JVM帮我们向操作系统借“一条线程”,也就是分配出一条执行路径,执行我们指定的代码。
那么就会涉及到一个问题:怎么指定?会不会好不容易借过来thread,结果执行的代码不是我们期望的?于是就会衍生出一个绑定关系:任务和线程如何绑定?绑定后thread就能执行指定任务。
也就是说,Java的这套Thread API本质上就是提供一种绑定关系,让线程和任务得以绑定。通俗点理解,Java的Thread就像张天师手中的天雷符,天雷符不是真正的天雷,但却可以向老天爷(操作系统)借一道天雷。
线程池逻辑
由于时间有限,马上要上班了。线程池部分先简单介绍一下思路。
上面以天雷比喻线程,那么我想问各位一句:如何复用一道天雷?好不容易向老天接了一道天雷,总不至于劈一次就结束吧,毕竟世间魑魅魍魉何其多!所以就要考虑复用天雷!而线程池就是为了复用天雷。
如何不让天雷消失?就是告诉它:魑魅魍魉除尽前,都不得消失!
所以线程池的复用逻辑很简单粗暴:如果任务没执行完,thread就不会消失。即使任务执行完了,我也要造成一种假象,让CPU觉得还没执行完,即阻塞线程!
总结一下线程池复用线程的核心逻辑:
- 持续执行任务
- 即使任务执行结束,也可以通过阻塞线程,不让线程结束,最终提为“线程复用”
简单版线程池
public class SimpleThreadPool {
/**
* 任务队列
*/
BlockingQueue<Runnable> workQueue;
/**
* 工作线程
*/
List<Worker> workers = new ArrayList<>();
/**
* 构造器
*
* @param poolSize 线程数
* @param workQueue 任务队列
*/
SimpleThreadPool(int poolSize, BlockingQueue<Runnable> workQueue) {
this.workQueue = workQueue;
// 创建线程,并加入线程池
for (int i = 0; i < poolSize; i++) {
Worker work = new Worker();
work.start();
workers.add(work);
}
}
/**
* 提交任务
*
* @param command
*/
void execute(Runnable command) {
try {
// 任务队列满了则阻塞
workQueue.put(command);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 工作线程,负责执行任务
*/
class Worker extends Thread {
public void run() {
// 循环获取任务,如果任务为空则阻塞等待
while (true) {
try {
Runnable task = workQueue.take();
task.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
复杂版线程池
作者:bravo1988
链接:https://www.zhihu.com/question/558457760/answer/2708771354
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
public class ThreadPool {
private final ReentrantLock mainLock = new ReentrantLock();
/**
* 工作线程
*/
private final List<Worker> workers = new ArrayList<>();
/**
* 任务队列
*/
private BlockingQueue<Runnable> workQueue;
/**
* 核心线程数
*/
private final int corePoolSize;
/**
* 最大线程数
*/
private final int maximumPoolSize;
/**
* 非核心线程最大空闲时间(否则销毁线程)
*/
private final long keepAliveTime;
public ThreadPool(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit timeUnit,
BlockingQueue<Runnable> workQueue) {
this.workQueue = workQueue;
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.keepAliveTime = timeUnit.toNanos(keepAliveTime);
}
public void execute(Runnable task) {
Assert.notNull(task, "task is null");
// 创建核心线程处理任务
if (workers.size() < corePoolSize) {
this.addWorker(task, true);
return;
}
// 尝试加入任务队列
boolean enqueued = workQueue.offer(task);
if (enqueued) {
return;
}
// 创建非核心线程处理任务
if (!this.addWorker(task, false)) {
// 非核心线程数达到上限,触发拒绝策略
throw new RuntimeException("拒绝策略");
}
}
private boolean addWorker(Runnable task, boolean core) {
int wc = workers.size();
if (wc >= (core ? corePoolSize : maximumPoolSize)) {
return false;
}
boolean workerStarted = false;
try {
Worker worker = new Worker(task);
final Thread thread = worker.getThread();
if (thread != null) {
mainLock.lock();
workers.add(worker);
thread.start();
workerStarted = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
mainLock.unlock();
}
return workerStarted;
}
private void runWorker(Worker worker) {
Runnable task = worker.getTask();
try {
// 循环处理任务
while (task != null || (task = getTask()) != null) {
task.run();
task = null;
}
} finally {
// 从循环退出来,意味着当前线程是非核心线程,而且需要被销毁
// Java的线程,既可以指代Thread对象,也可以指代JVM线程,一个Thread对象绑定一个JVM线程
// 因此,线程的销毁分为两个维度:1.把Thread对象从workers移除 2.JVM线程执行完当前任务,会自然销毁
workers.remove(worker); // 这里前后应该加锁,否则线程不安全。由于是demo,很多处理比较随意
}
}
private Runnable getTask() {
boolean timedOut = false;
// 循环获取任务
for (; ; ) {
// 是否需要检测超时:当前线程数超过核心线程
boolean timed = workers.size() > corePoolSize;
// 需要检测超时 && 已经超时了
if (timed && timedOut) {
return null;
}
try {
// 是否需要检测超时
// 1.需要:poll阻塞获取,等待keepAliveTime,等待结束就返回,不管有没有获取到任务
// 2.不需要:take持续阻塞,直到获取结果
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
@Getter
@Setter
private class Worker implements Runnable {
private Thread thread;
private Runnable task;
public Worker(Runnable task) {
this.task = task;
thread = new Thread(this);
}
@Override
public void run() {
runWorker(this);
}
}
}