1.准备工作
加入依赖:
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
logback.xml 配置如下
<?xml version="1.0" encoding="UTF-8"?>
<configuration
xmlns="http://ch.qos.logback/xml/ns/logback"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback logback.xsd">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{HH:mm:ss} [%t] %logger - %m%n</pattern>
</encoder>
</appender>
<logger name="c" level="debug" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
<root level="ERROR">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
为了后面方便,加入此工具类
package cn.yutian.n2.util;
import java.util.concurrent.TimeUnit;
public class Sleeper {
public static void sleep(int i) {
try {
TimeUnit.SECONDS.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void sleep(double i) {
try {
TimeUnit.MILLISECONDS.sleep((int) (i * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
线程是进程中的一个实体,线程本身是不会独立存在的。线程是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。在Java中,当我们启动main函数时其实就启动了一个JVM的进程,而main函数所在的线程就是这个进程中的一个线程,也称主线程。
线程是程序执行的一个路径,每一个线程都有自己的局部变量表、程序计数器(指向正在执行的指令指针)以及各自的生命周期,现代操作系统中一般不止一个线程在运行,当启动了一个 Java 虚拟机(JVM)时,从操作系统开始就会创建一个新的进程(JVM 进程),JVM 进程中将会派生或者创建很多线程。
2. Java 线程API介绍
1. 创建和运行线程
java 中有三种线程创建方式,分别为实现 Runnable 接口的 run 方法,继承 Thread重写 run 方法,使用 FutureTask 方式
方法一,继承 Thread重写 run 方法:
package com.yutian;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.ThreadTest")
public class ThreadTest {
public static class Thread01 extends Thread{
@Override
public void run() {
log.debug("hello");
}
}
public static void main(String[] args) {
Thread01 thread01 = new Thread01();
thread01.start();
log.debug("hello");
}
}需要注意的是:
- 当创建完 thread 对象后该线程并没有被启动执行,直到调用了 start 方法后才真正启动了线程。
- Java不支持多继承,如果继承了Thread类,那么就不能再继承其他类。
- 当前线程直接使用this就可以了,无须使用Thread.currentThread()方法
- 任务与代码没有分离,当多个线程执行一样的任务时需要多份代码
由于我们没有指定线程名,默认为main和Thread-01

更改名字
thread01.setName("T01");
匿名内部类写法:
package com.yutian;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.ThreadTest")
public class ThreadTest01 {
public static void main(String[] args) {
Thread thread01 = new Thread(){
@Override
public void run() {
log.debug("hello");
}
};
thread01.setName("T01");
thread01.start();
log.debug("hello");
}
}lamboda简化写法:
package com.yutian;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.ThreadTest")
public class ThreadTest01 {
public static void main(String[] args) {
Thread thread01 = new Thread(() -> log.debug("hello"));
thread01.setName("T01");
thread01.start();
log.debug("hello");
}
}
方法二,使用 Runnable 配合 Thread
package com.yutian;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.RunableTest")
public class RunableTest{
public static class Thread01 implements Runnable{
@Override
public void run() {
log.debug("hello");
}
}
public static void main(String[] args) {
Thread01 runable = new Thread01();
new Thread(runable).start();
new Thread(runable).start();
log.debug("hello");
}
}当多个线程执行一样的任务时可以添加参数进行任务区分

第二个参数,给线程取名:

Runnable接口源码:
带有@FunctionalInterface的都可以使用lamboda简化

匿名内部类写法:
package com.yutian;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.RunableTest")
public class RunableTest01 {
public static void main(String[] args) {
Runnable runable = new Runnable(){
@Override
public void run() {
log.debug("hello");
}
};
new Thread(runable).start();
new Thread(runable).start();
log.debug("hello");
}
}
lamboda简化写法:
package com.yutian;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.RunableTest")
public class RunableTest01 {
public static void main(String[] args) {
Runnable runable = () -> log.debug("hello");
new Thread(runable).start();
new Thread(runable).start();
log.debug("hello");
}
}
查看源码:

CTRL+ B,进入源码,可以看到传给了init方法

再次CTRL+ B,进入init方法,又是一个init方法

再次CTRL+ B,进入init

init中,至关重要的的一步,this.target = target

而这个成员变量在run方法,会用到,就和第一个继承 Thread重写 run 方法创建线程一样了

而同理,第一个继承 Thread重写 run 方法创建线程,因为重写了run方法了,就使用重写的run方法
方法三,FutureTask 配合 Thread
package com.yutian;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
@Slf4j(topic = "c.CallerTask")
public class CallerTask {
public static class Thread01 implements Callable<String>{
@Override
public String call() throws Exception {
log.debug("hello");
return "Hello JUC";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new Thread01());
new Thread(futureTask).start();
String result = futureTask.get();
System.out.println(result);
log.debug("hello");
}
}
源码:

CTRL+ B,进入源码

往上拉,找到类,其实现了RunnableFuture

而RunnableFuture实现了Runnable,所以才可以将其像第二种方法一样,传给Thread

而实现的Future<V>接口可以返回结果:

package com.yutian;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
@Slf4j(topic = "c.CallerTask")
public class CallerTask01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new Callable<String>(){
@Override
public String call() throws Exception {
log.debug("hello");
return "Hello JUC";
}
});
new Thread(futureTask).start();
String result = futureTask.get();
System.out.println(result);
log.debug("hello");
}
}
2 .观察多个线程同时运行
对于线程来说,是交替执行的,没有谁先谁后,不由我们控制
package com.yutian;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.TestMultiThread")
public class TestMultiThread {
public static void main(String[] args) {
new Thread(() -> {
while(true) {
log.debug("running");
}
},"t1").start();
new Thread(() -> {
while(true) {
log.debug("running");
}
},"t2").start();
}
}可以看到

3 .查看进程线程的方法
- windows
任务管理器可以查看进程和线程数,也可以用来杀死进程
查看进程
tasklist
杀死进程
taskkill
查看java进程
tasklist | findstr java
- linux
查看所有进程
ps -fe
查看所有java进程
ps -fe | grep java
查看某个进程(PID)的所有线程
ps -fT -p <PID>
杀死进程
kill <PID>
动态采集线程
top
查看某个进程(PID)的所有线程
top -H -p <PID>
- Java
命令查看所有 Java 进程
jps
查看某个 Java 进程(PID)的所有线程状态
jstack <PID>
jconsole来查看某个 Java 进程中线程的运行情况(图形界面)
jconsole:
- 从Java5开始,在JDK中自带的java监控和管理控制台。
- 用于对JVM中内存、线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监控工具
官方教程:Using JConsole – Java SE Monitoring and Management Guide (oracle.com)
远程连接:
使用代码:
public class Thread01 {
public static void main(String[] args) {
new Thread(() -> {
while(true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"t1").start();
new Thread(() -> {
while(true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"t2").start();
}
}将其上传到linux
先
javac Thread01.java
再
java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=是否认证 java类
如
java -Djava.rmi.server.hostname=192.168.188.201 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=12345 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false Thread01


4 .原理之线程运行
栈与栈帧:
Java Virtual Machine Stacks (Java 虚拟机栈)
我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
示例代码:
package cn.itcast.n3;
public class TestFrames {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
method1(20);
}
};
t1.setName("t1");
t1.start();
method1(10);
}
private static void method1(int x) {
int y = x + 1;
Object m = method2();
System.out.println(m);
}
private static Object method2() {
Object n = new Object();
return n;
}
}debug代码,产生main栈帧

点击下一步

进入新方法,产生新栈帧

再点击下一步

进入新方法,产生新栈帧

再继续

注意,在java中,线程运行和就绪都是RUNNING
debug还可以切换线程:

互不影响

线程上下文切换(Thread Context Switch):
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
- 线程的 cpu 时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念,就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
- 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
- Context Switch 频繁发生会影响性能
5 .常见方法
常见方法:
- start():启动一个新线程,在新的线程运行run方法中的代码,start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateExceptionrun
- run():新线程启动后会调用的方法。如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为
- join():等待线程运行结束
- join(long n):等待线程运行结束,最多等待n 毫秒
- getId():获取线程长整型的id。id 唯一
- getName():获取线程名
- setName(String):修改线程名
- getPriority():获取线程优先级
- setPriority(int):修改线程优先级。java中规定线程优先级是1~10的整数,较大的优先级能提高该线程被 CPU 调度的机率
- isInterrupted():判断是否被打断,不会清除打断标记
- isAlive():线程是否存活(还没有运行完毕)
- interrupt():打断线程,如果被打断线程正在 sleep, wait,join 会导致被打断的线程抛出 InterruptedException,并清除打断标记;如果打断的正在运行的线程,则会设置打断标记;park的线程被打断,也会设置打断标记
- interrupted():static方法,判断当前线程是否被打断。会清除打断标记
- currentThread() :static方法,获取当前正在执行的线程
- sleep(long n):static方法,让当前执行的线程休眠n毫秒,休眠时让出 cpu 的时间片给其它线程
- yield():static方法,提示线程调度器让出当前线程对CPU的使用。主要是为了测试和调试
6.start 与 run
调用 run
package cn.itcast.test;
import cn.itcast.Constants;
import cn.itcast.n2.util.FileReader;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test4")
public class Test4 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("running...");
}
};
t1.run();
log.debug("do other things...");
}
}输出

程序仍在 main 线程运行.
将上述代码的 t1.run() 改为
t1.start();

注意start()只能调用一次

使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
7. sleep 与 yield
sleep
1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test6")
public class Test6 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
log.debug("t1 state: {}", t1.getState());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1 state: {}", t1.getState());
}
}
2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException,所以我们要对其捕获。
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test7")
public class Test7 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("enter sleep...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
log.debug("wake up...");
e.printStackTrace();
}
}
};
t1.start();
Thread.sleep(1000);
log.debug("interrupt...");
t1.interrupt();
}
}
3. 睡眠结束后的线程未必会立刻得到执行,要等待cpu.
4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

其实,它底层调用的也是Thread.sleep(),只是进行了换算。

yield
1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程,当然,只会分给就绪的线程,堵塞的不行。
2. 具体的实现依赖于操作系统的任务调度器
线程优先级
- 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
- 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
- setPriority(int):修改线程优先级。java中规定线程优先级是1~10的整数,较大的优先级能提高该线程被 CPU 调度的机率
源码:

可以看到最大为10,最小为1,默认为5.

注意:
sleep 实现:在没有利用 cpu 来计算时,不要让 while(true) 空转浪费 cpu,这时可以使用 yield 或 sleep 来让出 cpu 的使用权给其他程序
package cn.itcast.test;
public class T11 {
public static void main(String[] args) {
while(true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}- 可以用 wait 或 条件变量达到类似的效果
- 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
- sleep 适用于无需锁同步的场景
8. join 方法详解
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test10")
public class Test10 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
},"t1");
t1.start();
//t1.join();
log.debug("结果为:{}", r);
log.debug("结束");
}
}
- 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
- 而主线程一开始就要打印 r 的结果,所以只能打印出 r=0
将注释去掉:
t1.join();
需要等待t1线程结束返回,才能继续运行main线程
最后输出r为10
注意:
线程A调用线程B的 join方法后会被阻塞,当其他线程调用了线程A的interrupt()方法中断了线程A时,线程A会抛出 InterruptedException 异常而返回。
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test10")
public class Test10 {
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("t1开始");
for (; ;) {
}
},"t1");
final Thread mainthread = Thread.currentThread();
Thread t2 = new Thread(() -> {
log.debug("t2开始");
sleep(1);
mainthread.interrupt();
log.debug("t2结束");
},"t1");
log.debug("结束");
t1.start();
t2.start();
t1.join();
}
}
9. interrupt 方法详解
Java中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。
- void interrupt()方法:中断线程,例如,当线程 A运行时,线程 B 可以调用线程 A的interrupt()方法来设置线程A的中断标志为true并立即返回。设置标志仅仅是设置标志,线程 A 实际并没有被中断,它会继续往下执行。如果线程 A 因为调用了wait系列函数、join方法或者sleep方法而被阻塞挂起,这时候若线程B调用线程A的interrupt()方法,线程A会在调用这些方法的地方抛出InterruptedException 异常而返回。
- boolean isInterrupted()方法:检测当前线程是否被中断,如果是返回true,否则返回 false.
- boolean interrupted()方法:检测当前线程是否被中断,如果是返回true,否则返回false,与isInterrupted不同的是,该方法如果发现当前线程被中断,则会清除中断标志(意思是再次判断会为false),并且该方法是static方法,可以通过Thread类直接调用。在 interrupted()内部是获取当前调用线程的中断标志而不是调用interrupted()方法的实例对象的中断标志。
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test12")
public class Test12 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(true) {
if(Thread.currentThread().isInterrupted()) {
log.debug("被打断了, 退出循环");
break;
}
}
}, "t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
}
}
还可以打断 park 线程
Park 和 Unpark 均是 LockSupport 类中的方法
//暂停当前线程 LockSupport.park(); //恢复某个线程 LockSupport.unpark(暂停线程对象);
注意:
- park 中的线程,处于 WAIT 状态
- unpark 既可以在 park 之前调用或之后调用,都是用来恢复某个线程的运行,简单的说,调用 unpark 后再调用 park 线程依然不会暂停。

interrupt() 方法可以打断 park 线程, 使其继续执行下去。
package com.dreams.t;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
import static com.dreams.t.Sleeper.sleep;
@Slf4j(topic = "c.Test14")
public class Test14 {
private static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
log.debug("park...");
LockSupport.park();
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
LockSupport.park();
log.debug("park...");
}
});
t1.start();
sleep(1);
t1.interrupt();
}
private static void test4() {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
log.debug("park...");
LockSupport.park();
log.debug("打断状态:{}", Thread.interrupted());
LockSupport.park();
log.debug("park...");
}
});
t1.start();
sleep(1);
t1.interrupt();
}
public static void main(String[] args) throws InterruptedException {
test4();
}
}如上,test3和test4两种不同状况,park 方法再次调用会失效,因为 park 线程只有打断状态为false才有效,已经调用了t1.interrupt()将状态设置为true了,所以只有Thread.interrupted()清空状态,重新变为false才能再次阻塞。
test3不会阻塞

test4则会

10 .不推荐的方法
不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁
- stop()停止线程运行
- suspend()挂起(暂停)线程运行
- resume()恢复线程运行
已弃用

11 .主线程与守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test15")
public class Test15 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
break;
}
}
log.debug("结束");
}, "t1");
// t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
log.debug("结束");
}
}会一直循环
而将注释去掉:
t1.setDaemon(true);
main线程结束,就结束了

注意:
- 垃圾回收器线程就是一种守护线程,程序停止了,垃圾回收器线程也会停止。
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求
12 .五种状态
从 操作系统 层面来描述的


- 初始状态(NEW):仅是在语言层面创建了线程对象,还未与操作系统线程关联,在没有start之前,该线程根本不存在,与用关键字new创建一个普通的Java对象没什么区别。
- 可运行状态(就绪状态)(RUNNABLE ):指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行,线程对象进入 RUNNABLE 状态必须调用 start 方法,具备执行的资格
- 运行状态(RUNNING ):指获取了 CPU 时间片运行中的状态,当 CPU 时间片用完,会从运行状态转换至可运行状态,会导致线程的上下文切换,一个正在 RUNNING 状态的线程事实上也是RUNNABLE的,但是反过来则不成立。
- 阻塞状态(BLOCKED):如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入阻塞状态,等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至可运行状态,与可运行状态的区别是,对阻塞状态的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
- 终止状态(TERMINATED):表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
13 .六种状态
这是从 Java API 层面来描述的
根据 Thread中的枚举类State ,分为六种状态。



- NEW 线程刚被创建,但是还没有调用 start() 方法
- RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统层面的可运行状态、运行状态和阻塞状态(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
- BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分
- TERMINATED 当线程代码运行结束
假设有线程 Thread t
情况 1 :NEW –> RUNNABLE
当调用 t.start() 方法时,由 NEW –> RUNNABLE
情况 2 :RUNNABLE <–> WAITING
t 线程用 synchronized(obj) 获取了对象锁后
调用 obj.wait() 方法时,t 线程从 RUNNABLE –> WAITING
调用 obj.notify() , obj.notifyAll() , t.interrupt() 时
- 竞争锁成功,t 线程从 WAITING –> RUNNABLE
- 竞争锁失败,t 线程从 WAITING –> BLOCKED
情况 3: RUNNABLE <–> WAITING
当前线程调用 t.join() 方法时,当前线程从 RUNNABLE –> WAITING
注意是当前线程在t 线程对象的监视器上等待
t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING –> RUNNABLE
情况 4: RUNNABLE <–> WAITING
当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE –> WAITING
调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING –>
RUNNABLE
情况 5 :RUNNABLE <–> TIMED_WAITING
t 线程用 synchronized(obj) 获取了对象锁后
调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE –> TIMED_WAITING
t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时
- 竞争锁成功,t 线程从 TIMED_WAITING –> RUNNABLE
- 竞争锁失败,t 线程从 TIMED_WAITING –> BLOCKED
情况 6: RUNNABLE <–> TIMED_WAITING
- 当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE –> TIMED_WAITING,注意是当前线程在t 线程对象的监视器上等待
- 当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从TIMED_WAITING –> RUNNABLE
情况 7: RUNNABLE <–> TIMED_WAITING
- 当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE –> TIMED_WAITING
- 当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING –> RUNNABLE
情况 8: RUNNABLE <–> TIMED_WAITING
- 当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线程从 RUNNABLE –> TIMED_WAITING
- 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从TIMED_WAITING–> RUNNABLE
情况 9 :RUNNABLE <–> BLOCKED
- t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE –> BLOCKED
- 持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争成功,从 BLOCKED –> RUNNABLE ,其它失败的线程仍然 BLOCKED
情况 10: RUNNABLE <–> TERMINATED
当前线程所有代码运行完毕,进入 TERMINATED
注意,在idea里显示名字有差异:

参考资料
黑马程序员深入学习Java并发编程:https://www.bilibili.com/video/BV16J411h7Rd
汪文君著《Java高并发编程详解》
翟陆续著《Java并发编程之美》


