一、Java线程模型
- 在Linux中的线程模型分为两种:ULT(用户线程模型)线程的的生命周期是由应用自己去管理。KLT(内核线程模型)用户的生命周期是由Linux的内核进行管理。在java中使用的是KLT的线程模型,Java线程和操作系统层面上的线程是一一对应的。
- 线程作为一组任务任务的执行单元自然也有自己的生命周期。在Java中的线程生命周期分为如下的几个:
- 新建状态(NEW)。当使用new关键字创建实例化一个线程对象的时候的线程就是处于新建状态。
- 可运行态(RUNNABLE)。当线程调用start方法之后线程就是处于这个状态。可运行状态又包含两个状态:就绪态,线程已经做好准备等待CPU的调度,运行态,已经得到CPU的执行时间片执行任务。
- 无限期等待状态(WAITING)。当调用Object.wait()、object.join()、LockSupport.park()方法的时候线程会由RUNNABLE状态转换为WAITING状态
- 超时等待状态(TIMED_WAITING)。当调用Thread.sleep(long)、Object.wait(long)、Object.join(long)、LockSupport.parkNacos()、LockSupport.parkUntil()方法时线程会由RUNNABLE状态变化为无限期等待状态。
- 阻塞状态(BLOCKED)。当线程进入同步方法或者是同步代码块的时候线程会处于阻塞状态
- 终止(TERMINATED)。终止状态当线程运行完成之后线程最终就会处于这个状态
二、在Java中实现线程的几种方式
在前面说了线程的几种状态转换。那在java中实现线程的方式有几种呢?
- 通过实现的Runnable接口
class RunnableRunner implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable接口");
}
}
- 通过继承Thread类
class ThreadRunner extends Thread{
@Override
public void run() {
System.out.println("继承Thread类");
}
}
- 通过实现Callable。接受有泛型参数以及返回值的
class CallableRunner implements Callable<String> {
@Override
public String call() throws Exception {
return "通过Callable实现任务提交";
}
}
- 测试方法以及输出
public class ThreadImpl {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//通过实现Runnable接口
Thread runnableRunnerThread = new Thread(new RunnableRunner());
runnableRunnerThread.start();
//继承Thread类
Thread threadRunner = new ThreadRunner();
threadRunner.start();
//通过Callable实现任务
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,1,
100, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
Future<String> result = threadPoolExecutor.submit(new CallableRunner());
System.out.println(result.get());
Thread.sleep(10000);
}
}
// 输出结果
实现Runnable接口
继承Thread类
通过Callable实现任务提交
三、线程池的使用
- 如何去初始化一个线程池:在Java中提供如下的构造方法去吃初始化一个线程池
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
初始化参数详解:
- corePoolSize:核心线程数。核心线程数,在线程池空闲没有任务的时候也要保证存活的线程。在这里就像我们在项目开发中可能某一个项目非常大需要人手比较多。需要更多人来一起开发才可以将需求准时完成。于是你作为项目的负责人就像你的上层老大申请给你加人手。但是需求完成之后,多出人会还回去。只留下项目的负责人。那在这里核心线程数就类似项目组核心那几个人。不管在闲都会存在。
- maximumPoolSize:最大线程数。还是上面说的案例你向你的老大申请给你人手但是也不可能无限的给你加人,得有个人数限制。这个上限值就和maximumPoolSize类似。
- keepAliveTime:空闲线程的存活时间。假如你的老大给你加了五十个人来帮你完成一个大项目。当项目的各个模块陆续完成之后他们就开始闲下来没事干。那闲下来一段时间之后确认没事就可以他们还回去。
- unit:空闲时间的单位。是空闲两个小时、两天还是两个月才把他们还回去。这个unit就是表示具体空闲的时间的单位。
- workQueue:等待队列。当任务太多来不及处理的时候就会将需要处理的任务暂时放入等待队列中。还是上面的案例就是当有很多业务方的需求过来的时候,但是呢你很忙手上的事情还没有处理完成。让他们先登记在一个需求表格里面在你手上事情处理完成之后在按照他们登记的先后顺序去处理他们的需求。等待队列实现的逻辑也是类似的
- threadFactory:线程工厂。就是生产线程用于处理任务的线程。你的老大给你加的人可能是来自他下面的某个项目组或者某几个项目组。那这些项目组可以类比成生产线程的工厂。他们都是从这个里面来的那必定具备他所在项目组的某些特性
- handler:拒绝策略。当你需要处理的需求实在太多即使给你加了人还是不能处理完。这个时候就需要执行一些策略保证某些需要的顺利完成。有可能是你告诉给你提需求的人说你们自己处理我这边实在太忙没有时间处理。或者他说他的需求的优先级非常高,让他去和当前在需求表格里面最前面提需求的人聊一聊看能不能让他的需求先处理。但是这个排在最前面人不同意让他先处理,这个是时候你没有办法你只能告诉你老大让你老大和他的老大去商量这个事,就类似在线程池里面的抛出异常。这个人在临走的时候还diss你,你很气你TM是傻*吧,又不是我不给你处理,但是他diss你让你很不爽,老子不给你处理大不了不干了,这就类似在线程池中的直接丢弃任务的策略一样。
- 线程池提供哪些重要api供外部使用
void execute(Runnable command);
执行Runnable类型的任务<T> Future<T> submit
执行Runnable或者Callable类型的任务,并且返回这个任务的future对象void shutdown();
完成已经提交的任务,不再接受新任务,执行完成之后关闭线程池List<Runnable> shutdownNow();
停止所有正在执行的任务- 提交任务以及在提交任务之后想要获取处理的结果如何处理
在实际的开发工作中有这样的需求使用线程池不需要知道任务运行的结果,第二种是需要知道线程运行的结果。那这样的方式我们如何去实现呢?如下所示
public class ThreadPoolExecutorRunner {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4,
100, TimeUnit.SECONDS, new LinkedBlockingDeque<>(), r -> {
Thread thread = new Thread(r);
thread.setName("ThreadPoolExecutorRunner-thread");
return thread;
},new ThreadPoolExecutor.AbortPolicy());
// 不需要获取任务执行的结果
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName());
System.out.println("不需要Task结果");
});
//需要task结果
Future<String> taskResult = threadPool.submit(() -> "需要Task结果");
System.out.println(taskResult.get());
Thread.sleep(1000);
}
}
// 输出结果
ThreadPoolExecutorRunner-thread
不需要Task结果
需要Task结果
但是有些任务是非常重要不能出现丢失的,对于这种情况可以自己实现RejectedExecutionHandler接口自定义拒绝策略。将消息放入消息中间件等线程池闲下来了再去处理。
四、从线程池创建到一个任务的运行剖析其内部原理
- ThreadPoolExecutor继承关系图
从继承的关系图可以看出都实现了Exector这个关键的接口,实现了自己提交任务的逻辑 - 线程池重点属性
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
ctl是对线程池运行状态和线程数据量进行描述的一个字段。包含了两部分信息:线程池的运行状态和线程池内有效的线程数。那是如何使用一个Integer类型的值来保存的呢?如下所示:
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
首先我们看CAPACITY的初始化的值如下:
000 11111111111111111111111111111
runStateOf的计算方式是将CAPACITY按位取反在与输入的结果做逻辑按位与。结果如下:
111 0000000000000000000000000000
这样不低二十九位是任何值按位与之后都是0,高三位按位与之后还是原来的值。线程池的生命周期的代码如下所示:
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
//对应的二进制如下:
001 00000000000000000000000000000 ->STOP
000 00000000000000000000000000000 ->SHUTDOWN
010 00000000000000000000000000000 ->TIDYING
011 00000000000000000000000000000 ->TERMINATED
111 00000000000000000000000000000 ->RUNNING
同理在描述线程池中的线程的计算方式
private static int workerCountOf(int c) { return c & CAPACITY; }
//CAPACITY的二进制
000 11111111111111111111111111111
不管这个数与任何数逻辑与得到的高的三位永远是0.
经过上面的分析可以得到如下图:
5. 线程池的生命周期
6. 任务提交流程以及执行流程源码分析
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 线程刚创建的状态一个线程都没有,优先创建出来的线程是核心线程。最大线程数 = 核心线程数 + 非核心线程
// 1 当当前线程池中线程数量小于核心线程的时候会一直去创建核心线程
// TODO: 2021/6/27 不同参数的线程池参数线程的运行状态
if (workerCountOf(c) < corePoolSize) {
// 创建的是核心线程,在线程没有创建慢的时候会一直创建核心线程
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2 当前线程的核心线程已经满了,当核心线程已经满了就会将任务丢入阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 3 当核心线程和阻塞队列都满了就会再去创建非核心线程
else if (!addWorker(command, false))
// 4 非核心线程已经满了执行拒绝策略
reject(command);
}
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
// 判断线程的生命状态是否是Running状态
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 5、把当前的任务封装为一个worker
w = new Worker(firstTask);
// 7、执行任务的线程
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 8、放入works的结合中
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 9、执行的是worker的run方法
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 6、线程工厂去创建一个线程,将自己作为构造参数传递进去
this.thread = getThreadFactory().newThread(this);
}
// 10 、直线线程池中的先任务
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 11、不断的去取任务去执行。这也是线程池实现重用的的逻辑。getTask()从队列中获取任务
// 就是在这里实现线程的重用的
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
// 15、处理在执行任务的时候抛出异常的逻辑。抛出异常之后后面的还是可以继续执行
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 14、执行线程回收的逻辑
// 15、处理在执行任务的时候抛出异常的逻辑。抛出异常之后后面的还是可以继续执行
processWorkerExit(w, completedAbruptly);
}
}
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
// 16、删除执行过程中异常的线程
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
// 17、如果当前线程的状态是RUNNING和SHUTDOWN
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 17、只要当前的线程池的状态不是stop重新创建一个线程,重新执行任务
addWorker(null, false);
}
}
执行流程图:
其实总结起来如下图所示:
五、线程池参数设置多少合适
线程池的参数设置分为不同的场景:IO密集型和CPU密集型。前者cpu大部分时间处于空闲时间。后者CPU会很繁忙。那么线程数设置经验值为:
最佳线程数 =CPU 核数 * [ 1 +(I/O 耗时 / CPU 耗时)]
但是要想得到最优的结果还是需要根据实际场景通过压测得到最合理的值