若文章内容或图片失效,请留言反馈。
部分素材来自网络,若不小心影响到您的利益,请联系博主删除。
- 视频链接:https://www.bilibili.com/video/av81461839
- 视频下载:https://pan.baidu.com/s/1VLxzhbEeyHcIrSplVoNSAQ?2006zzp#list/path=%2F(无提取码)
- 配套资料:https://pan.baidu.com/s/1lSDty6-hzCWTXFYuqThRPw( 提取码:5xiu)
写这篇博客旨在制作笔记,方便个人在线阅览,巩固知识。
博客的内容主要来自视频内容和资料中提供的学习笔记。
参考博客
- JAVA 并发编程 (上) 超详细笔记 从入门到深入原理
- 操作系统为什么会有上下文这种概念?带你深入理解上下文基础知识
- Java 多线程 interrupt 中断阻塞
参考书籍
- 《深入理解 JAVA 虚拟机 | JVM 高级特性与最佳实践》 周志明 著
学习该课程前的预备知识
pom.xml 的依赖
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.10version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
dependencies>
logback.xml 的配置如下
<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%npattern>
encoder>
appender>
<logger name="c" level="debug" additivity="false">
<appender-ref ref="STDOUT"/>
logger>
<root level="ERROR">
<appender-ref ref="STDOUT"/>
root>
configuration>
不过我自己实际导入的依赖是参考自博客:JAVA 并发编程 (上) 超详细笔记 从入门到深入原理
pom.xml 的依赖
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.11version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.22version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.22version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiterartifactId>
<version>RELEASEversion>
<scope>compilescope>
dependency>
dependencies>
resources
目录下的 logback.xml
<configuration scan="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{HH:mm:ss} [%t] %logger - %m%npattern>
encoder>
appender>
<logger name="c" level="debug" additivity="false">
<appender-ref ref="STDOUT"/>
logger>
<root level="ERROR">
<appender-ref ref="STDOUT"/>
root>
configuration>
本章内容
参考博客:操作系统为什么会有上下文这种概念?带你深入理解上下文基础知识
上下文是从英文 context 翻译过来,指的是一种环境。
相对于进程而言,就是进程执行时的环境。
具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。
为什么会有上下文这种概念?
单核 cpu 下,线程实际还是 串行执行 的。
操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows 下时间片最小约为 15 毫秒)分给不同的程序使用。
只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是同时运行的 。
总结为一句话就是: 微观串行,宏观并行 。
一般会将这种线程轮流使用 CPU 的做法称为 并发(concurrent)
CPU | 时间片 1 | 时间片 2 | 时间片 3 | 时间片 4 |
---|---|---|---|---|
core | 线程 1 | 线程 2 | 线程 3 | 线程 4 |
多核 cpu 下,每个 核(core) 都可以调度运行线程,这时候线程可以是 并行(parallel) 的。
CPU | 时间片 1 | 时间片 2 | 时间片 3 | 时间片 4 |
---|---|---|---|---|
core1 | 线程 1 | 线程 2 | 线程 3 | 线程 4 |
core2 | 线程 4 | 线程 4 | 线程 2 | 线程 2 |
Rob Pike - 百度百科(go 语言之父)
引用 Rob Pike 的一段描述:
举例
从调用方的角度来讲同步和异步的概念
注意:同步在多线程中还有另外一层意思,即让多个线程步调一致。
多线程可以让方法执行变为异步的(即不要巴巴干等着)。
比如读取磁盘文件时,读取操作需要花费 5 秒钟。
如果没有线程调度机制,这 5 秒 cpu 什么都做不了,其它代码都得暂停 … …
异步调用演示视频:https://www.bilibili.com/video/BV16J411h7Rd?p=7
本博客主要是学习做笔记,方便在线阅览用。具体的代码实现和细节部分还请见视频和资料。之后不再赘述这点。
此时我们尚未学习到相关的 api,相关的环境搭建和代码实现,了解即可。
比如在项目中,视频文件需要转换格式等操作比较费时,这时开一个新线程处理视频转换,避免阻塞主线程。
tomcat 的异步 servlet 也是类似的目的:让用户线程处理耗时较长的操作,避免阻塞 tomcat 的工作线程。
ui 程序中,开线程进行其他操作,避免阻塞 ui 线程
充分利用多核 cpu 的优势,提高运行效率。
想象下面的场景,执行 3 个计算,最后将计算结果汇总。
计算 1 花费 10 ms
计算 2 花费 11 ms
计算 3 花费 9 ms
汇总需要 1 ms
注意:需要在多核 cpu 才能提高效率,单核仍然时是轮流执行
1.设计
具体的设计见资料中提供的并发编程_应用.pdf
此时我们尚未学习到相关的 api,相关的环境搭建和代码实现,了解即可。
2.结论
本章内容
方法一:直接使用 Thread
方法二:使用 Runnable 配置 Thread
方法三:FutureTask 配合 Thread
// 创建线程对象
Thread t = new Thread() {
public void run() {
// 要执行的任务
}
};
// 启动线程
t.start();
代码演示
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test1")
public class Test_1 {
public static void main(String[] args) {
//构造方法的参数是给线程指定名字(推荐)
Thread thread = new Thread("thread_1") {
@Override
//run 方法内实现了要执行的任务
public void run() {
log.debug("running");
}
};
//也可以使用 void setName(String name)方法来设置线程名
//thread.setName("t1");
thread.start();
log.debug("running");
}
}
打印结果
11:58:49 [main] c.Test1 - running
11:58:49 [thread_1] c.Test1 - running
把 线程 和 任务(要执行的代码) 分开
Thread
代表线程Runnable
可运行的任务(线程要执行的代码)Runnable runnable = new Runnable() {
public void run() {
// 要执行的任务
}
};
// 创建线程对象
Thread t = new Thread(runnable);
// 启动线程
t.start();
代码演示
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test2")
public class Test_2 {
public static void main(String[] args) {
//创建任务对象
Runnable runnable = new Runnable() {
@Override
public void run() {
//要执行的任务
log.debug("HelloWorld!");
}
};
Thread thread = new Thread(runnable, "t2");
thread.start();
}
}
输出结果
12:32:36 [t2] c.Test2 - HelloWorld!
Java 8 以后可以使用 lambda 精简部分代码
//创建任务对象
Runnable runnable = () -> log.debug("HelloWorld!");
分析 Thread 的源码,理清它与 Runnable 的关系:https://www.bilibili.com/video/BV16J411h7Rd?p=14
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况
代码演示
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
@Slf4j(topic = "c.Test3")
public class Test_3 {
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);
return 100;
}
});
/**
* Thread(Runnable target, String name)
* 参数 1 为任务对象,参数 2 为线程名
*/
Thread thread = new Thread(task, "线程名_t1");
thread.start();
//主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task.get();
log.debug("结果:{}", result);
}
}
打印结果
16:47:33 [线程名_t1] c.Test3 - running... ...
16:47:34 [main] c.Test3 - 结果:100
旨在理解
观察多个线程同时进行的演示视频:https://www.bilibili.com/video/BV16J411h7Rd?p=16
tasklist
查看进程taskkill
杀死进程比如我们可以通过 tasklist | findstr java
来查找 java 相关的进程
再通过 taskkill /F /PID 具体的pid
来强制终止进程。
ps -fe
查看所有进程ps -fT -p
查看某个进程(PID)的所有线程kill
杀死进程top
动态查看线程信息(按大写 H 切换是否显示线程)top -H -p
查看某个进程(PID)的所有线程例如我们可以上传这样一个文件:TestMultiThread.java
,具体内容如下
之后我们再使用命令编译该文件
javac TestMultiThread.java
再用 java 程序来运行它
java TestMultiThread
使用相关命令来查看进程的情况
如上图所示,若是要强制终止 TestMultiThread 进程,使用命令 kill -9 12943
即可。
jps
命令查看所有 Java 进程jstack
查看某个 Java 进程(PID)的所有线程状态jconsole
来查看某个 Java 进程中线程的运行情况(图形界面)java -Djava.rmi.server.hostname=ip地址 \
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=连接端口 \
-Dcom.sun.management.jmxremote.ssl=是否安全连接(写 true 或者 false) \
-Dcom.sun.management.jmxremote.authenticate=是否认证(写 true 或者 false) \
TestMultiThread
/etc/hosts
文件将 127.0.0.1
映射至主机名之后打开 jconsole,使用 ip地址:端口号
连接即可。
如果要认证访问,还需要做如下步骤
jmxremote.password
文件jmxremote.password
和 jmxremote.access
文件的权限为 600 即文件所有者可读写controlRole
(用户名),R&D
(密码)Java Virtual Machine Stacks(Java 虚拟机栈)
JVM 中由 堆、栈、方法区 所组成,其中栈内存是给谁用的呢?
其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。
线程运行原理-栈帧 Debug 的演示视频:https://www.bilibili.com/video/BV16J411h7Rd?p=20
下面那个图看看就好,具体的演示过程和解析还是要看下面的视频
线程运行原理-栈帧图解 的演示视频:https://www.bilibili.com/video/BV16J411h7Rd?p=21
程序计数器(Program Counter Register)-百度百科
程序计数器是用于存放下一条指令所在单元的地址的地方。
当执行一条指令时,首先需要根据 PC 中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为 “取指令” 。
与此同时,PC 中的地址或自动加 1 或由转移指针给出下一条指令的地址。
此后经过分析指令,执行指令。
完成第一条指令的执行,而后根据 PC 取出第二条指令的地址,如此循环,执行每一条指令。
参考博客:JAVA 并发编程 (上) 超详细笔记 从入门到深入原理
程序计数器是负责记录要执行的代码行(字节码指令),然后把将要执行的字节码指令交给 cpu 执行。(简单理解)
多线程的栈帧运行:线程的栈内存是相互独立的,每个线程拥有自己独立的栈内存。栈内存里面有多个栈帧,它们互不干扰。
参考书籍:《深入理解 JAVA 虚拟机 | JVM 高级特性与最佳实践》 周志明 著
线程上下文切换(Thread Context Switch)
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码。
当上下文切换(Context Switch)发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态。
Java 中对应的概念就是程序计数器(Program Counter Register)
方法名 | 功能说明 | 注意 |
---|---|---|
start() |
启动一个新线程, 在新的线程运行 run 方法中的代码 |
start 方法只是让线程进入就绪, 里面代码不一定立刻运行(CPU 的时间片还没分给它)。 每个线程对象的 start 方法只能调用一次, 如果调用了多次会出现 IllegalThreadStateException。 |
run() |
新线程启动后会调用的方法 | 如果在构造 Thread 对象时传递了 Runnable 参数, 则线程启动后会调用 Runnable 中的 run 方法, 否则默认不执行任何操作。 但可以创建 Thread 的子类对象,来覆盖默认行为。 |
join() |
等待线程运行结束 | |
join(long n) |
等待线程运行结束, 最多等待 n 毫秒 |
|
getId() |
获取线程长整型的 id | id 唯一 |
getName() |
获取线程名 | |
setName(String) |
修改线程名 | |
getPriority() |
获取线程优先级 | |
setPriority(int) |
修改线程优先级 | java 中规定线程优先级是 1~10 的整数, 较大的优先级能提高该线程被 CPU 调度的机率。 |
getState() |
获取线程状态 | Java 中线程状态是用 6 个 enum 表示, 分别是:NEW、RUNNABLE、BLOCKED、 WAITING、TIMED_WAITING、TERMINATED |
isInterrupted() |
判断是否被打断 | 不会清除 打断标记 |
isAlive() |
线程是否存活(还没有运行完毕) | |
interrupt() |
通知目标线程中断,设置中断标志位 | 如果被打断线程正在 sleep,wait,join, 会导致被打断的线程抛出 InterruptedException, 并清除 打断标记 ; 如果打断的正在运行的进程,则会设置 打断标记; park 的线程被打断,也会设置 打断标记 |
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
interrupted() |
static | 判断当前线程是否被打断 | 会清除 打断标记 |
currentThread() |
static | 获取当前正在执行的线程 | |
sleep(long n) |
static | 让当前执行的线程休眠 n 毫秒, 休眠时让出 cpu 的时间片给其它线程 |
|
yield() |
static | 提示线程调度器让出当前线程对 CPU 的使用 | 主要是为了测试和调试 |
调用 run
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
FileReader.read(Constants.MP4_FULL_PATH);
}
};
t1.run();
log.debug("do other things ...");
}
输出
19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms
19:39:18 [main] c.TestStart - do other things ...
程序仍在 main 线程运行,FileReader.read()
方法调用还是同步的
调用 start
将上述代码的 t1.run()
改为 1.start();
输出
19:41:30 [main] c.TestStart - do other things ...
19:41:30 [t1] c.TestStart - t1
19:41:30 [t1] c.FileReader - read [1.mp4] start ...
19:41:35 [t1] c.FileReader - read [1.mp4] end ... cost: 4542 ms
程序在 t1 线程运行,FileReader.read()
方法调用是异步的
小结
run
是在主线程中执行了 run
,没有启动新的线程,这样的话并不能起到一个异步的效果。start
是启动新的线程,通过新的线程间接执行 run
中的代码sleep
sleep
会让 当前线程 从 Running 进入 Timed Waiting 状态(阻塞)interrupt
方法打断正在睡眠的线程,这时 sleep
方法会抛出 InterruptedExceptionsleep
代替 Thread 的 sleep
来获得更好的可读性yield
yield
会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程sleep 和 yield 的区别
sleep()
使当前线程进入停滞状态,所以执行 sleep()
的线程在指定的时间内肯定不会执行;yield()
只是使当前线程重新回到可执行状态,所以执行 yield()
的线程有可能在进入到可执行状态后马上又被执行。sleep()
可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会;yield()
只能使同优先级的线程有执行的机会。限制对 CPU 的使用(sleep
实现)
在没有利用 cpu 来计算时,不要让 while(true)
空转浪费 cpu
这时可以使用 yield
或 sleep
来让出 cpu 的使用权给其他程序
可以用 wait
或 条件变量达到类似的效果
不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
sleep
适用于无需锁同步的场景
观察下面的代码,猜测 r 可能会输出的值。
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.setName("ThreadName-t1");
t1.start();
log.debug("结果为:{}", r);
log.debug("结束");
}
输出结果
19:50:24 [main] c.Test10 - 开始
19:50:24 [ThreadName-t1] c.Test10 - 开始
19:50:24 [main] c.Test10 - 结果为:0
19:50:24 [main] c.Test10 - 结束
19:50:25 [ThreadName-t1] c.Test10 - 结束
Process finished with exit code 0
分析
解决方法
sleep
行不行?为什么?
join
,加在 t1.start()
之后即可输出结果
21:05:03 [main] c.Test10 - 开始
21:05:03 [ThreadName-t1] c.Test10 - 开始
21:05:04 [ThreadName-t1] c.Test10 - 结束
21:05:04 [main] c.Test10 - 结果为:10
21:05:04 [main] c.Test10 - 结束
从调用方的角度来讲
等待多个结果
问:下面代码 cost 大约多少秒?
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(1);
r1 = 10;
});
Thread t2 = new Thread(() -> {
sleep(2);
r2 = 20;
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
分析
如果颠倒两个 join 呢?
最终的花费时间都是一样的
21:40:32 [main] c.TestJoin - r1: 10 r2: 20 cost: 2013
没等够时间
设置 t1 线程 sleep(2);
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test3();
}
public static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(2);
r1 = 10;
});
long start = System.currentTimeMillis();
t1.start();
// 线程执行结束会导致 join 结束
log.debug("join begin");
t1.join(1500);
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
输出结果
20:52:15.623 [main] c.TestJoin - r1: 0 r2: 0 cost: 1502
等够时间
设置 t1 线程 sleep(1);
,其余代码同上
输出结果
20:48:01.320 [main] c.TestJoin - r1: 10 r2: 0 cost: 1010
如果打断的正在运行的进程,则会设置 打断标记,其为一个布尔值。
参考博客:Java 多线程 interrupt 中断阻塞
interrupt()
方法并不是中断线程,而是中断阻塞状态,或者将线程的 打断标记 设置为 true。interrupt()
只是将 打断标记 设置为 true,线程本身运行状态不受影响。interrupt()
会中断阻塞状态,使其转换成非阻塞状态,并清除 打断标记
sleep()
、wait()
和 join()
。sleep()
、wait()
和 join()
,线程本身还在继续运行。打断 sleep
,wait
,join
的线程(这三个方法都会让线程进入阻塞状态)
这里以 sleep
为例
Thread t1 = new Thread(() -> {
log.debug("sleep ...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
log.debug("打断标记:{}",t1.isInterrupted());
输出
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 cn.itcast.n2.util.Sleeper.sleep(Sleeper.java:8)
at cn.itcast.n4.TestInterrupt.lambda$test1$3(TestInterrupt.java:59)
at java.lang.Thread.run(Thread.java:745)
21:18:10.374 [main] c.TestInterrupt - 打断状态: false
打断正常运行的线程, 不会清空打断状态
Thread t1 = new Thread(() -> {
while (true) {
boolean interrupted = Thread.currentThread().isInterrupted();
if (interrupted) {
log.debug("被打断,退出循环");
break;
}
}
}, "t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt...");
t1.interrupt();
输出
08:30:10 [main] c.Test12 - interrupt...
08:30:10 [t1] c.Test12 - 被打断,退出循环
Two Phase Termination
在一个线程 T1 中如何 “优雅” 地终止线程 T2 ?
这里的 优雅 指的是给 T2 一个料理后事的机会。
使用线程对象的 stop()
方法停止线程
stop
方法会真正杀死线程使用 System.exit(int)
方法停止线程
代码演示
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test13")
public class Test13 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTerminal twoPhaseTerminal = new TwoPhaseTerminal();
twoPhaseTerminal.start();
Thread.sleep(3500);
twoPhaseTerminal.stop();
}
}
@Slf4j(topic = "c.TwoPhaseTerminal")
class TwoPhaseTerminal {
private Thread monitor;
//启动监控线程
public void start() {
monitor = new Thread(() -> {
while (true) {
Thread current = Thread.currentThread();
if (current.isInterrupted()) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);//情况 1
log.debug("执行监控记录");//情况 2
} catch (InterruptedException e) {
e.printStackTrace();
//重新设置打断标记
current.interrupt();
}
}
});
monitor.start();
}
//停止监控线程
public void stop() {
monitor.interrupt();
}
}
输出结果
18:11:13 [Thread-0] c.TwoPhaseTerminal - 执行监控记录
18:11:14 [Thread-0] c.TwoPhaseTerminal - 执行监控记录
18:11:15 [Thread-0] c.TwoPhaseTerminal - 执行监控记录
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at org.example.chapter02.c2_6b.TwoPhaseTerminal.lambda$start$0(Test13.java:34)
at java.lang.Thread.run(Thread.java:748)
18:11:16 [Thread-0] c.TwoPhaseTerminal - 料理后事
细节1
如果在 执行监控记录时,执行了打断,并不会出现异常,但这种情况无需额外做处理。
因为这个时候的打断标记并不会被重置、清除,打断标记依旧为真,仍然可以进入下一轮循环检验。
细节2: interrupt()
、interrupted()
、isInterrupted()
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
interrupt() |
通知目标线程中断,设置线程中断的标志位 | 如果被打断线程正在 sleep,wait,join, 会导致被打断的线程抛出 InterruptedException, 并清除 打断标记; 如果打断的正在运行的进程,则会设置 打断标记; park 的线程被打断,也会设置 打断标记 |
|
interrupted() |
static | 判断当前线程是否被打断 | 会清除 打断标记 |
isInterrupted() |
判断是否被打断 | 不会清除 打断标记 |
private static void test3() throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park...");
LockSupport.park();
log.debug("unpark...");
log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
}, "t1");
t1.start();
sleep(0.5);
t1.interrupt();
}
输出
21:11:52.795 [t1] c.TestInterrupt - park...
21:11:53.295 [t1] c.TestInterrupt - unpark...
21:11:53.295 [t1] c.TestInterrupt - 打断状态:true
但如果打断标记已经是 true, 则 park 会失效。
此时可以使用 Thread.interrupted()
清除打断状态
还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁
方法名 | static |
功能说明 |
---|---|---|
stop() |
停止线程运行 | |
suspend() |
挂起(暂停)线程运行 | |
resume() |
恢复线程运行 |
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。
有一种特殊的线程叫做守护线程。
只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
log.debug("开始运行...");
Thread t1 = new Thread(() -> {
log.debug("开始运行...");
sleep(2);
log.debug("运行结束...");
}, "daemon");
// 设置该线程为守护线程
t1.setDaemon(true);
t1.start();
sleep(1);
log.debug("运行结束...");
输出
08:26:38.123 [main] c.TestDaemon - 开始运行...
08:26:38.213 [daemon] c.TestDaemon - 开始运行...
08:26:39.215 [main] c.TestDaemon - 运行结束...
注意
这是从 操作系统 层面来描述的
这是从 Java API 层面来描述的
根据 Thread.State 枚举,分为六种状态
start()
方法start()
方法之后
具体演示可见视频:https://www.bilibili.com/video/BV16J411h7Rd?p=46
本章的重点在于掌握