最近有点懒散,没什么比较有深度的产出。刚好想重新研读一下JUC
线程池的源码实现,在此之前先深入了解一下Java
中的线程实现,包括线程的生命周期、状态切换以及线程的上下文切换等等。编写本文的时候,使用的JDK
版本是11。
在「JDK1.2之后」,Java线程模型已经确定了基于操作系统原生线程模型实现。因此,目前或者今后的JDK版本中,操作系统支持怎么样的线程模型,在很大程度上决定了Java虚拟机的线程如何映射,这一点在不同的平台上没有办法达成一致,虚拟机规范中也未限定Java线程需要使用哪种线程模型来实现。线程模型只对线程的并发规模和操作成本产生影响,对于Java程序来说,这些差异是透明的。
对应Oracle Sun JDK
或者说Oracle Sun JVM
而言,它的Windows版本和Linux版本都是使用「一对一的线程模型」实现的(如下图所示)。
j-t-l-s-1.png
也就是一条Java
线程就映射到一条轻量级进程(「Light Weight Process」)中,而一条轻量级线程又映射到一条内核线程(「Kernel-Level Thread」)。我们平时所说的线程,往往就是指轻量级进程(或者通俗来说我们平时新建的java.lang.Thread
就是轻量级进程实例的一个"句柄",因为一个java.lang.Thread
实例会对应JVM
里面的一个JavaThread
实例,而JVM
里面的JavaThread
就应该理解为轻量级进程)。前面推算这个线程映射关系,可以知道,我们在应用程序中创建或者操作的java.lang.Thread
实例最终会映射到系统的内核线程,如果我们恶意或者实验性无限创建java.lang.Thread
实例,最终会影响系统的正常运行甚至导致系统崩溃(可以在Windows
开发环境中做实验,确保内存足够的情况下使用死循环创建和运行java.lang.Thread
实例)。
线程调度方式包括两种,协同式线程调度和抢占式线程调度。
线程调度方式 | 描述 | 劣势 | 优势 |
---|---|---|---|
协同式线程调度 | 线程的执行时间由线程本身控制,执行完毕后主动通知操作系统切换到另一个线程上 | 某个线程如果不让出CPU执行时间可能会导致整个系统崩溃 | 实现简单,没有线程同步的问题 |
抢占式线程调度 | 每个线程由操作系统来分配执行时间,线程的切换不由线程自身决定 | 实现相对复杂,操作系统需要控制线程同步和切换 | 不会出现一个线程阻塞导致系统崩溃的问题 |
Java
线程最终会映射为系统内核原生线程,所以Java
线程调度最终取决于系操作系统,而目前主流的操作系统内核线程调度基本都是使用抢占式线程调度。也就是可以死记硬背一下:「Java线程是使用抢占式线程调度方式进行线程调度的」。
很多操作系统都提供线程优先级的概念,但是由于平台特性的问题,Java中的线程优先级和不同平台中系统线程优先级并不匹配,所以Java线程优先级可以仅仅理解为“「建议优先级」”,通俗来说就是java.lang.Thread#setPriority(int newPriority)
并不一定生效,「有可能Java线程的优先级会被系统自行改变」。
Java
线程的状态可以从java.lang.Thread
的内部枚举类java.lang.Thread$State
得知:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
这些状态的描述总结成图如下:
j-t-l-s-3
「线程状态之间关系切换」图如下:
j-t-l-s-2
下面通过API注释和一些简单的代码例子分析一下Java线程的状态含义和状态切换。
「API注释」:
/**
* Thread state for a thread which has not yet started.
*
*/
NEW,
❝线程实例尚未启动时候的线程状态。
❞
一个刚创建而尚未启动(尚未调用Thread#start()
方法)的Java线程实例的就是处于NEW
状态。
public class ThreadState {
public static void main(String[] args) throws Exception {
Thread thread = new Thread();
System.out.println(thread.getState());
}
}
// 输出结果
NEW
「API注释」:
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
❝可运行状态下线程的线程状态。可运行状态下的线程在Java虚拟机中执行,但它可能执行等待操作系统的其他资源,例如处理器。
❞
当Java线程实例调用了Thread#start()
之后,就会进入RUNNABLE
状态。RUNNABLE
状态可以认为包含两个子状态:READY
和RUNNING
。
READY
:该状态的线程可以被线程调度器进行调度使之更变为RUNNING
状态。RUNNING
:该状态表示线程正在运行,线程对象的run()
方法中的代码所对应的的指令正在被CPU执行。当Java线程实例Thread#yield()
方法被调用时或者由于线程调度器的调度,线程实例的状态有可能由RUNNING
转变为READY
,但是从线程状态Thread#getState()
获取到的状态依然是RUNNABLE
。例如:
public class ThreadState1 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(()-> {
while (true){
Thread.yield();
}
});
thread.start();
Thread.sleep(2000);
System.out.println(thread.getState());
}
}
// 输出结果
RUNNABLE
「API注释」:
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* is waiting for a specified thread to terminate.
*/
WAITING,
❝等待中线程的状态。一个线程进入等待状态是由于调用了下面方法之一:不带超时的Object#wait() 不带超时的Thread#join() LockSupport.park() 一个处于等待状态的线程总是在等待另一个线程进行一些特殊的处理。例如:一个线程调用了Object#wait(),那么它在等待另一个线程调用对象上的Object#notify()或者Object#notifyAll();一个线程调用了Thread#join(),那么它在等待另一个线程终结。
❞
WAITING
是「无限期的等待状态」,这种状态下的线程不会被分配CPU执行时间。当一个线程执行了某些方法之后就会进入无限期等待状态,直到被显式唤醒,被唤醒后,线程状态由WAITING
更变为RUNNABLE
然后继续执行。
RUNNABLE转换为WAITING的方法(无限期等待) | WAITING转换为RUNNABLE的方法(唤醒) |
---|---|
Object#wait() | Object#notify() |
Thread#join() | - |
LockSupport.part() | LockSupport.unpart(thread) |
其中Thread#join()
方法相对比较特殊,它会阻塞线程实例直到线程实例执行完毕,可以观察它的源码如下:
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
可见Thread#join()
是在线程实例存活的时候总是调用Object#wait()
方法,也就是必须在线程执行完毕isAlive()
为false(意味着线程生命周期已经终结)的时候才会解除阻塞。
基于WAITING
状态举个例子:
public class ThreadState3 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(()-> {
LockSupport.park();
while (true){
Thread.yield();
}
});
thread.start();
Thread.sleep(50);
System.out.println(thread.getState());
LockSupport.unpark(thread);
Thread.sleep(50);
System.out.println(thread.getState());
}
}
// 输出结果
WAITING
RUNNABLE
「API注释」:
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
❝定义了具体等待时间的等待中线程的状态。一个线程进入该状态是由于指定了具体的超时期限调用了下面方法之一:Thread.sleep() 带超时的Object#wait() 带超时的Thread#join() LockSupport.parkNanos() LockSupport.parkUntil()
❞
TIMED WAITING
就是「有限期等待状态」,它和WAITING
有点相似,这种状态下的线程不会被分配CPU执行时间,不过这种状态下的线程不需要被显式唤醒,只需要等待超时限期到达就会被VM
唤醒,有点类似于现实生活中的闹钟。
RUNNABLE转换为TIMED WAITING的方法(有限期等待) | TIMED WAITING转换为RUNNABLE的方法(超时解除等待) |
---|---|
Object#wait(timeout) | - |
Thread#sleep(timeout) | - |
Thread#join(timeout) | - |
LockSupport.parkNanos(timeout) | - |
LockSupport.parkUntil(timeout) | - |
举个例子:
public class ThreadState4 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(()-> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//ignore
}
});
thread.start();
Thread.sleep(50);
System.out.println(thread.getState());
Thread.sleep(1000);
System.out.println(thread.getState());
}
}
// 输出结果
TIMED_WAITING
TERMINATED
「API注释」:
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
❝此状态表示一个线程正在阻塞等待获取一个监视器锁。如果线程处于阻塞状态,说明线程等待进入同步代码块或者同步方法的监视器锁或者在调用了Object#wait()之后重入同步代码块或者同步方法。
❞
BLOCKED
状态也就是阻塞状态,该状态下的线程不会被分配CPU执行时间。线程的状态为BLOCKED
的时候有两种可能的情况:
❝A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method
❞
synchronized
代码块或者synchronized
方法,在此等待获取锁的过程线程都处于阻塞状态。❝reenter a synchronized block/method after calling Object#wait()
❞
2 . 线程X步入synchronized
代码块或者synchronized
方法后(此时已经释放监视器锁)调用Object#wait()
方法之后进行阻塞,当接收其他线程T调用该锁对象Object#notify()/notifyAll()
,但是线程T尚未退出它所在的synchronized
代码块或者synchronized
方法,那么线程X依然处于阻塞状态(注意API注释中的「reenter」,理解它场景2就豁然开朗)。
更加详细的描述可以参考笔者之前写过的一篇文章:深入理解Object提供的阻塞和唤醒API
针对上面的场景1举个简单的例子:
public class ThreadState6 {
private static final Object MONITOR = new Object();
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(()-> {
synchronized (MONITOR){
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
//ignore
}
}
});
Thread thread2 = new Thread(()-> {
synchronized (MONITOR){
System.out.println("thread2 got monitor lock...");
}
});
thread1.start();
Thread.sleep(50);
thread2.start();
Thread.sleep(50);
System.out.println(thread2.getState());
}
}
// 输出结果
BLOCKED
针对上面的场景2举个简单的例子:
public class ThreadState7 {
private static final Object MONITOR = new Object();
private static final DateTimeFormatter F = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws Exception {
System.out.println(String.format("[%s]-begin...", F.format(LocalDateTime.now())));
Thread thread1 = new Thread(() -> {
synchronized (MONITOR) {
System.out.println(String.format("[%s]-thread1 got monitor lock...", F.format(LocalDateTime.now())));
try {
Thread.sleep(1000);
MONITOR.wait();
} catch (InterruptedException e) {
//ignore
}
System.out.println(String.format("[%s]-thread1 exit waiting...", F.format(LocalDateTime.now())));
}
});
Thread thread2 = new Thread(() -> {
synchronized (MONITOR) {
System.out.println(String.format("[%s]-thread2 got monitor lock...", F.format(LocalDateTime.now())));
try {
MONITOR.notify();
Thread.sleep(2000);
} catch (InterruptedException e) {
//ignore
}
System.out.println(String.format("[%s]-thread2 releases monitor lock...", F.format(LocalDateTime.now())));
}
});
thread1.start();
thread2.start();
// 这里故意让主线程sleep 1500毫秒从而让thread2调用了Object#notify()并且尚未退出同步代码块,确保thread1调用了Object#wait()
Thread.sleep(1500);
System.out.println(thread1.getState());
System.out.println(String.format("[%s]-end...", F.format(LocalDateTime.now())));
}
}
// 某个时刻的输出如下:
[2019-06-20 00:30:22]-begin...
[2019-06-20 00:30:22]-thread1 got monitor lock...
[2019-06-20 00:30:23]-thread2 got monitor lock...
BLOCKED
[2019-06-20 00:30:23]-end...
[2019-06-20 00:30:25]-thread2 releases monitor lock...
[2019-06-20 00:30:25]-thread1 exit waiting...
场景2中:
Object#notify()
后睡眠2000毫秒再退出同步代码块,释放监视器锁。Object#wait()
,此时它已经释放了监视器锁,所以线程2成功进入同步块,线程1处于API注释中所述的reenter a synchronized block/method
的状态。reenter
状态并且打印其线程状态,刚好就是BLOCKED
状态。这三点看起来有点绕,多看几次多思考一下应该就能理解。
「API注释」:
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
❝终结的线程对应的线程状态,此时线程已经执行完毕。
❞
TERMINATED
状态表示线程已经终结。一个线程实例只能被启动一次,准确来说,只会调用一次Thread#run()
方法,Thread#run()
方法执行结束之后,线程状态就会更变为TERMINATED
,意味着线程的生命周期已经结束。
举个简单的例子:
public class ThreadState8 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(() -> {
});
thread.start();
Thread.sleep(50);
System.out.println(thread.getState());
}
}
// 输出结果
TERMINATED
多线程环境中,当一个线程的状态由RUNNABLE
转换为非RUNNABLE
(BLOCKED
、WAITING
或者TIMED_WAITING
)时,相应线程的上下文信息(也就是常说的Context
,包括CPU
的寄存器和程序计数器在某一时间点的内容等等)需要被保存,以便线程稍后恢复为RUNNABLE
状态时能够在之前的执行进度的基础上继续执行。而一个线程的状态由非RUNNABLE
状态进入RUNNABLE
状态时可能涉及恢复之前保存的线程上下文信息并且在此基础上继续执行。这里的对「线程的上下文信息进行保存和恢复的过程」就称为上下文切换(Context Switch
)。
线程的上下文切换会带来额外的性能开销,这包括保存和恢复线程上下文信息的开销、对线程进行调度的CPU
时间开销以及CPU
缓存内容失效的开销(线程所执行的代码从CPU
缓存中访问其所需要的变量值要比从主内存(RAM
)中访问响应的变量值要快得多,但是「线程上下文切换会导致相关线程所访问的CPU缓存内容失效,一般是CPU的L1 Cache
和L2 Cache
」,使得相关线程稍后被重新调度到运行时其不得不再次访问主内存中的变量以重新创建CPU
缓存内容)。
在Linux
系统中,可以通过vmstat
命令来查看全局的上下文切换的次数,例如:
$ vmstat 1
对于Java
程序的运行,在Linux
系统中也可以通过perf
命令进行监视,例如:
$ perf stat -e cpu-clock,task-clock,cs,cache-reference,cache-misses java YourJavaClass
参考资料中提到Windows
系统下可以通过自带的工具perfmon
(其实也就是任务管理器)来监视线程的上下文切换,实际上笔者并没有从任务管理器发现有任何办法查看上下文切换,通过搜索之后发现了一个工具:Process Explorer。运行Process Explorer
同时运行一个Java
程序并且查看其状态:
j-t-l-s-4.png
因为打了断点,可以看到运行中的程序的上下文切换一共7000多次,当前一秒的上下文切换增量为26(因为笔者设置了Process Explorer
每秒刷新一次数据)。
如果项目在生产环境中运行,不可能频繁调用Thread#getState()
方法去监测线程的状态变化。JDK本身提供了一些监控线程状态的工具,还有一些开源的轻量级工具如阿里的Arthas,这里简单介绍一下。
jvisualvm
是JDK自带的堆、线程等待JVM指标监控工具,适合使用于开发和测试环境。它位于JAVA_HOME/bin
目录之下。
j-t-l-s-5.png
其中线程Dump
的按钮类似于下面要提到的jstack
命令,用于导出所有线程的栈信息。
jstack
是JDK自带的命令行工具,功能是用于获取指定PID的Java进程的线程栈信息。例如本地运行的一个IDEA
实例的PID
是11376,那么只需要输入:
jstack 11376
然后控制台输出如下:
j-t-l-s-6.png
另外,如果想要定位具体Java进程的PID
,可以使用jps
命令。
JMC
也就是Java Mission Control
,它也是JDK自带的工具,提供的功能要比jvisualvm
强大,包括MBean的处理、线程栈已经状态查看、飞行记录器等等。
j-t-l-s-7.png
理解Java线程状态的切换和一些监控手段,更有利于日常开发多线程程序,对于生产环境出现问题,通过监控线程的栈信息能够快速定位到问题的根本原因(通常来说,目前比较主流的MVC
应用(准确来说应该是Servlet
容器如Tomcat
)都是通过一个线程处理一个单独的请求,当请求出现阻塞的时候,导出对应处理请求的线程基本可以定位到阻塞的精准位置,如果使用消息队列例如RabbitMQ
,消费者线程出现阻塞也可以利用相似的思路解决)。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/q6fkI8chSUyse4SOIEDEMQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。