一、创建和运行线程
pom.xml依赖如下:
<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.7</version>
</dependency>
</dependencies>
1.1 创建运行线程:Thread
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
log.debug("running");
}
};
t.setName("t1");
t.start();
log.debug("running");
}
}
输出:
21:36:28.304 [main] DEBUG c.Test1 - running
21:36:28.304 [t1] DEBUG c.Test1 - running
1.2 创建运行线程:Runable配合Thread
把【线程】和【任务】(要执行的代码)分开
- Thread代表线程
- Runnable可运行的任务(线程要执行的代码)
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
log.debug("running");
}
};
Thread t = new Thread(r, "t2");
t.start();
}
}
使用lambda表达式简化:
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
Runnable r = () -> log.debug("running");
Thread t = new Thread(r, "t2");
t.start();
}
}
再简化:
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
Thread t = new Thread(() -> log.debug("running"), "t2");
t.start();
}
}
1.3 Thread与Runnable的关系
Thread
实现了Runnable
class Thread implements Runnable {...}
所以,在Thread
内要实现Runnable
接口内的方法run()
。可以看到,当target不为空时,在run()
方法中又调用target.run()
。
@Override
public void run() {
if (target != null) {
target.run();
}
}
所以接下来要看看target
是什么,在初始化方法init()
中。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
this.target = target;
...
}
所以,target
就是一个实现Runnable
接口所创建的对象。
所以,如果创建了一个Runnable
对象给Thread,那么Thread
运行的就是一个Runnable
对象中的run
方法;如果没创建Runnable
对象,就需要重写run
方法,Thread
将运行这个重写的run
方法。
方法1是把线程和任务合并在了一起,方法2是把线程和任务分开了,具有如下优点:
- 用Runnable更容易与线程池等高级API配合
- 用Runnable让任务类脱离了Thread继承体系,更灵活
1.4 创建运行线程:FutureTask配合Thread
@Slf4j(topic = "c.Test3")
public class Test3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("running...");
Thread.sleep(1000);
log.debug("running...");
return 100;
}
});
Thread t = new Thread(task, "t1");
t.start();
// 阻塞
log.debug("{}", task.get());
log.debug("over");
}
}
只有当log.debug("{}", task.get());
运行完才会执行后面的语句,主线程将阻塞在这里。
二、观察多个和线程同时运行
@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.1 windows
- 任务管理器可以查看进程和线程数,也可以用来杀死进程
- 查看进程:
tasklist
- 杀死进程:
taskkill
- 查找指定进程:
tasklist | findstr 进程包含的字符串
- 强制杀死进程:
taskkill /F /PID 进程号
3.2 linux
- 查看所有进程:
ps -ef
- 查看java进程:
ps -ef | grep java
- 查看java进程:
jps
- 杀死java进程:
kill 进程号
- 查看java某进程的线程:
top -H -p 进程号
- 查看java某进程的状态:
jstack 进程号
java图形化界面:jconsole
四、线程运行原理
4.1 栈与栈帧
Java Virtual Machine Stacks (Java 虚拟机栈)
我们都知道JVM中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。
- 每个栈由多个栈帧(Frame) 组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
4.1.1 测试栈帧
public class TestFrames {
public static void main(String[] args) {
method1(10);
}
private static void method1(int x) {
int y = x +1;
Object m = method2();
System.out.println(m);
}
private static Object method2() {
Object o = new Object();
return o;
}
}
在method1(10)
位置打上断点,debug调试程序:
详细过程:https://www.bilibili.com/video/BV16J411h7Rd?p=21
4.1.2 观察不同线程的栈帧
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 o = new Object();
return o;
}
}
在method1处打上端点。
断点模式选择Thread
debug进行调试,可以选择不同的栈帧进行调试:
4.2 线程上下文切换
线程上下文切换(Thread Context Switch)
因为以下一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码
- 线程的cpu时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了sleep、 yield、 wait、 join、 park、 synchronized、 lock 等方法
当Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条jvm指令的执行地址, 是线程私有的
- 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
- Context Switch频繁发生会影响性能
五、常见方法
方法名 | 功能说明 | 注意 |
---|---|---|
start() | 启动一个新线程,在新的线程运行run方法中的代码 | start方法只是让线程进入就绪,里面代码不一定立刻运行(CPU的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException |
run() | 新线程启动后会调用的方法 | 如果在构造Thread对象时传递了Runnable 参数,则线程启动后会调用Runnable中的run方法,否则默认不执行任何操作。但可以创建Thread的子类对象,来覆盖默认行为 |
join() | 等待线程运行结束 | |
join(long n) | 等待线程运行结束,最多等待n毫秒 | n毫秒内线程还没有结束就不等了 |
getId() | 获取线程长整型的id | id唯一 |
getName() | 获取线程名 | |
setName(String) | 修改线程名 | |
getPriority() | 获取线程优先级 | |
setPriority(int) | 修改线程优先级 | java中规定线程优先级是1~10的整数,较大的优先级能提高该线程被CPU调度的机率 |
getState | 获取线程状态 | Java中线程状态是用6个enum表示,分别为: NEW,RUNNABLE, BLOCKED, WAITING, TIMED _WAITNG,TERMINATED |
isInterrupted() | 判断当前线程是否被打断 | 不会清除打断标记 |
isAlive() | 线程是否存活(还没有运行完毕) | |
interrupt() | 打断线程 | 如果被打断线程正在sleep,wait, join 会导致被打断的线程抛出InterruptedException,并清除打断标记 ;如果打断的正在运行的线程,则会设置打断标记 ;park 的线程被打断,也会设置打断标记 |
currentThead() | 获取当前正在执行的线程 | |
sleep(long n) | 让当前执行的线程休眠n毫秒,休眠时让出cpu的时间片给其它线程. | |
yield() | 提示线程调度器让出当前线程对CPU的使用. | 主要为了测试和调试 |
5.1 start()与run()
直接调用run()方法,就仅仅是调用了run()方法,没有启动线程,并不会提高效率。
@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...");
}
};
System.out.println(t1.getState());
t1.run(); // 仅仅调用了run()方法,没有启动线程
System.out.println(t1.getState());
}
}
运行结果
NEW
20:34:52.813 [main] DEBUG c.Test4 - running...
NEW
使用start()开启线程:
@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...");
}
};
System.out.println(t1.getState());
t1.start();
System.out.println(t1.getState());
}
}
运行结果:
NEW
RUNNABLE
20:35:47.879 [t1] DEBUG c.Test4 - running...
5.2 sleep()
- 调用sleep会让当前线程从Running进入Timed Waiting状态
- 其它线程可以使用interrupt 方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用TimeUnit的sleep代替Thread的sleep来获得更好的可读性
测试1:调用sleep会让当前线程从Running进入Timed Waiting状态
@Slf4j(topic = "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());
}
}
运行结果:
20:41:03.324 [main] DEBUG test6 - t1 state: RUNNABLE
20:41:03.827 [main] DEBUG test6 - t1 state: TIMED_WAITING
解释:
程序启动时,主线程先运行,此时t1线程还未进入睡眠状态,当主线程等待了500ms后,t1线程此时已经进入睡眠状态,此时主线程获取的t1线程状态为睡眠状态。
测试2:其它线程可以使用interrupt 方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
@Slf4j(topic = "test7")
public class Test7 {
public static void main(String[] args) {
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();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("interrupt...");
t1.interrupt();
}
}
t1线程启动后,进入睡眠状态持续2000ms。主线程睡眠1000ms后打断t1线程。
运行结果:
20:47:50.398 [t1] DEBUG test7 - enter sleep...
20:47:51.397 [main] DEBUG test7 - interrupt...
20:47:51.397 [t1] DEBUG test7 - wake up
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at Chapter3.Test7$1.run(Test7.java:23)
测试4:建议用TimeUnit的sleep代替Thread的sleep来获得更好的可读性
@Slf4j(topic = "test8")
public class Test8 {
public static void main(String[] args) {
log.debug("enter");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("end");
}
}
睡眠前后各打印一次,输出结果:
20:52:46.795 [main] DEBUG test8 - enter
20:52:47.797 [main] DEBUG test8 - end
案例:sleep()防止CPU占用100%
在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield 或sleep来让出cpu的使用
权给其他程序
while (true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- 可以用wait或条件变量达到类似的效果
- 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
- sleep适用于无需锁同步的场景
5.3 yield()
- 调用yield会让当前线程从Running进入Rummable状态,然后调度执行其它同优先级的线程。如果这时没有同优先级的线程,那么不能保证让当前线程暂停的效果
- 具体的实现依赖于操作系统的任务调度器
5.4 线程优先级
线程优先级会提示(hint) 调度器优先调度该线程,但它仅仅是一个提示, 调度器可以忽略它
如果cpu比较忙,那么优先级高的线程会获得更多的时间片,但cpu闲时,优先级几乎没作用
@Slf4j(topic = "test9")
public class Test9 {
public static void main(String[] args) {
Runnable task1 = () -> {
int count = 0;
while (true) {
System.out.println("----->1 " + count++);
}
};
Runnable task2 = () -> {
int count = 0;
while (true) {
// Thread.yield();
System.out.println(" ----->2 " + count++);
}
};
Thread t1 = new Thread(task1, "t1");
Thread t2 = new Thread(task2, "t2");
// t1.setPriority(Thread.MIN_PRIORITY);
// t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
通过yeild()或是优先级的设置,可以起到一定效果,让其中的一个线程的count增长的快一些。
5.5 join()
5.5.1 只创建一个线程,等待线程运行结束
@Slf4j(topic = "test10")
public class Test10 {
static int r = 0;
public static void main(String[] args) {
test1();
}
private static void test1() {
log.debug("start");
Thread t1 = new Thread(() -> {
log.debug("start");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("end");
r = 10;
}, "t1");
t1.start();
log.debug("r is {}", r);
log.debug("end");
}
}
运行结果:
10:04:14.629 [main] DEBUG test10 - start
10:04:14.658 [t1] DEBUG test10 - start
10:04:14.658 [main] DEBUG test10 - r is 0
10:04:14.659 [main] DEBUG test10 - end
10:04:14.669 [t1] DEBUG test10 - end
分析:
因为主线程和线程t1是并行执行的,t1 线程需要1秒之后才能算出r=10
而主线程一开始就要打印r的结果,所以只能打印出r=0
解决方法:
用join, 加在t1.start()之后即可
@Slf4j(topic = "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("start");
Thread t1 = new Thread(() -> {
log.debug("start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("end");
r = 10;
}, "t1");
t1.start();
t1.join(); // 加上这句就可以了
log.debug("r is {}", r);
log.debug("end");
}
}
运行结果:
10:07:36.843 [main] DEBUG test10 - start
10:07:36.872 [t1] DEBUG test10 - start
10:07:37.872 [t1] DEBUG test10 - end
10:07:37.872 [main] DEBUG test10 - r is 10
10:07:37.873 [main] DEBUG test10 - end
以调用方角度来讲,如果
- 需要等待结果返回,才能继续运行就是同步
- 不需要等待结果返回,就能继续运行就是异步
5.5.2 创建两个线程,等待线程运行结束
当有两个线程同时运行并使用join时,线程1需要睡眠1s,线程2需要睡眠2s。
此时程序的总运行时间需要2s,t1.join()
和t2.join()
互换位置也一样。
5.5.3 有时效的jon
@Slf4j
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
// t1线程运行2s
Thread t1 = new Thread(() -> {
log.debug("start");
try {
TimeUnit.SECONDS.sleep(2);
log.debug("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
t1.start();
t1.join(1000);
log.debug("end");
}
}
运行结果:
10:25:36.407 [t1] DEBUG Chapter3.TestJoin - start
10:25:37.406 [main] DEBUG Chapter3.TestJoin - end
10:25:38.410 [t1] DEBUG Chapter3.TestJoin - end
从运行时间上看,主线程只等待了1s。而t1线程花费2s才运行完。
5.6 interrupt()
5.6.1 打断sleep,wait,join线程
@Slf4j
public class Test11 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("sleep....");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("interrupt");
t1.interrupt();
log.debug("打断标记:{}", t1.isInterrupted());
}
}
运行结果:
10:42:50.898 [t1] DEBUG Chapter3.Test11 - sleep....
10:42:51.897 [main] DEBUG Chapter3.Test11 - interrupt
10:42:51.897 [main] DEBUG Chapter3.Test11 - 打断标记:false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at Chapter3.Test11.lambda$main$0(Test11.java:24)
at java.lang.Thread.run(Thread.java:748)
5.6.2 打断正常运行的线程
@Slf4j
public class Test12 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
boolean interrupted = Thread.currentThread().isInterrupted();
if (interrupted) {
log.debug("被打断,退出循环");
break;
}
}
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
log.debug("interrupt");
t1.interrupt();
}
}
运行结果:
10:48:21.350 [main] DEBUG Chapter3.Test12 - interrupt
10:48:21.352 [t1] DEBUG Chapter3.Test12 - 被打断,退出循环
通过验证打断标记来决定是否退出循环,否则即使打断t1线程,while循环也不会结束。
5.6.3 两阶段终止模式
Two Phase Termination
在一个线程T1中如何“优雅”终止线程T2;这里的【优雅】指的是给T2一个料理后事的机会。
错误思路:
- 使用线程对象的stop()方法停止线程,stop方法会真正杀死线程。如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁。
- 使用System.exit(int)方法停止线程。目的仅是停止一个线程,但这种做法会让整个程序都停止。
正确思路:
@Slf4j(topic = "test13")
public class Test13 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
twoPhaseTermination.start();
Thread.sleep(3500);
twoPhaseTermination.stop();
}
}
@Slf4j(topic = "twoPhaseTermination")
class TwoPhaseTermination {
private Thread monitor;
// 启动监控线程
public void start() {
monitor = new Thread(() -> {
while (true) {
Thread currentThread = Thread.currentThread();
if (currentThread.isInterrupted()) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000); // 情况1:被打断时,打断标记为加,抛出InterruptedException异常
log.debug("执行监控记录"); // 情况2:被打断时,打断标记置为真
} catch (InterruptedException e) {
e.printStackTrace();
// 重新设置打断标记
currentThread.interrupt();
}
}
});
monitor.start();
}
// 停止监控线程
public void stop() {
monitor.interrupt();
}
}
运行结果
11:16:51.748 [Thread-0] DEBUG twoPhaseTermination - 执行监控记录
11:16:52.750 [Thread-0] DEBUG twoPhaseTermination - 执行监控记录
11:16:53.750 [Thread-0] DEBUG twoPhaseTermination - 执行监控记录
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at Chapter3.TwoPhaseTermination.lambda$start$0(Test13.java:39)
at java.lang.Thread.run(Thread.java:748)
11:16:54.247 [Thread-0] DEBUG twoPhaseTermination - 料理后事
注意:
- 正常运行的线程被打断时,会设置打断标记(打断标记置为true)。
- sleep、wait、join的线程被打断时,会抛出InterruptedException异常,会清除打断标记(打断标记置为false)。
5.6.4 打断park线程
@Slf4j
public class Test14 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
t1.interrupt();
}
}
运行结果:
11:26:13.031 [t1] DEBUG Chapter3.Test14 - park...
11:26:14.030 [t1] DEBUG Chapter3.Test14 - unpark...
11:26:14.030 [t1] DEBUG Chapter3.Test14 - 打断状态:true
如果不打断,将一直卡在LockSupport.park()
这个位置。
六、主线程与守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
@Slf4j
public class Test15 {
public static void main(String[] args) throws InterruptedException {
log.debug("start");
Thread t1 = new Thread(() -> {
log.debug("start");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("end");
}, "daemon");
t1.setDaemon(true);
t1.start();
TimeUnit.SECONDS.sleep(1);
log.debug("end");
}
}
将t1设置为守护线程,当主线程结束时,守护线程被强制结束。
运行结果:
12:30:25.900 [main] DEBUG Chapter3.Test15 - start
12:30:25.929 [daemon] DEBUG Chapter3.Test15 - start
12:30:26.930 [main] DEBUG Chapter3.Test15 - end
垃圾回收器线程就是一种守护线程
Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待它们处理完当前请求
七、线程状态
7.1 五种状态
从操作系统层描述,有五种状态
- 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
- 【可运行状态】 (就绪状态) 指该线程已经被创建(与操作系统线程关联),可以由CPU调度执行
- 【运行状态】指获取了CPU时间片运行中的状态。当CPU时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
- 【阻塞状态】
- 如果调用了阻塞API,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入【阻塞状态】
- 等BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与[可运行状态]的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考
虑调度它们
- 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
7.2 六种状态
从JAVA API层描述,根据Thread.State枚举,分为六种状态:
NEW
:线程刚被创建,还没有调用start()方法RUNNABLE
:当调用了start() 方法之后,注意,Java API层面的RUNNABLE状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【阻塞状态】 (由于 BIO导致的线程阻塞,在Java里无法区分,仍然认为是可运行)BLOCKED
,WAITING
,TIMED_WAITING
都是Java API层面对【阻塞状态】的细分,后面会在状态换节一详述TERMINATED
当线程代码运行结束
测试6种状态
@Slf4j
public class Test16 {
public static void main(String[] args) {
// NEW
Thread t1 = new Thread(() -> {
log.debug("running...");
},"t1");
// RUNNABLE
Thread t2 = new Thread(() -> {
while (true) {
}
},"t2");
t2.start();
// TERMINATED
Thread t3 = new Thread(() -> {
log.debug("running...");
},"t3");
t3.start();
// TIMED_WAITING
Thread t4 = new Thread(() -> {
synchronized (Test16.class) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t4");
t4.start();
// WAITING
Thread t5 = new Thread(() -> {
try {
t4.join();
log.debug("running...");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t5");
t5.start();
// BLOCKED
Thread t6 = new Thread(() -> {
synchronized (Test16.class) {
log.debug("running");
}
},"t6");
t6.start();
log.debug("t1 state: {}", t1.getState());
log.debug("t2 state: {}", t2.getState());
log.debug("t3 state: {}", t3.getState());
log.debug("t4 state: {}", t4.getState());
log.debug("t5 state: {}", t5.getState());
log.debug("t6 state: {}", t6.getState());
}
}
NEW
:线程没有start()
RUNNABLE:while
循环,线程正在运行
TERMINATED
:线程运行完毕
TIMED_WAITING
:线程睡眠种sleep
WAITING
:线程等待join
BLOCKED
:线程阻塞