3. Java 线程
3.1 创建和运行线程
方法一,直接使用 Thread
// 创建线程对象Thread t = new Thread() {public void run() {// 要执行的任务}};// 启动线程t.start();
lambda写法
new Thread(()->{//TODO...}).start();
例如
Thread thread = new Thread(() -> log.info("running"));thread.setName("I am a thread");thread.start();
运行结果
18:45:54 [I am a thread] com.zhr.thread.TestLog - running
方法二,使用 Runnable 配合 Thread
Thread 代表线程
Runnable 可运行的任务(线程要执行的代码)
例如
// 创建任务对象Runnable task2 = new Runnable() {@Overridepublic void run() {// 要执行的任务log.debug("hello");}};// 创建线程对象// 参数1 是任务对象; 参数2 是线程名字,推荐Thread t2 = new Thread(task2, "t2");// 启动线程t2.start();
运行结果
18:51:11 [t2] com.zhr.thread.Test2 - hello
原理之 Thread 与 Runnable 的关系
分析 Thread 的源码,理清它与 Runnable 的关系
小结
- 方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了
- 用 Runnable 更容易与线程池等高级 API 配合
- 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
方法三,FutureTask 配合 Thread
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况
结果public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建任务对象FutureTask<Integer> task3 = new FutureTask<>(() -> {log.debug("calling");return 100;});// 参数1 是任务对象; 参数2 是线程名字,推荐new Thread(task3, "t3").start();// 主线程阻塞,同步等待 task 执行完毕的结果Integer result = task3.get();log.debug("结果是:{}", result);}
19:21:37 [t3] com.zhr.thread.Test3 - calling19:21:37 [main] com.zhr.thread.Test3 - 结果是:100
方法四,继承Thread
示例 ```java @Slf4j public class Test4 { public static void main(String[] args) {
} }MyThread myThread = new MyThread();myThread.setName("my thread hi");myThread.start();
@Slf4j class MyThread extends Thread { @Override public void run() { log.info(“running”); } }
结果```java23:47:58 [my thread hi] com.zhr.thread.MyThread - running
3.2 多线程同时运行特点
3.3 查看进程线程的方法
windows
任务管理器可以查看进程和线程数,也可以用来杀死进程tasklist 查看进程tasklist | findstr "QQ"
C:\Users\ZL>tasklist /FI "IMAGENAME eq QQBrowser.exe"映像名称 PID 会话名 会话# 内存使用========================= ======== ================ =========== ============QQBrowser.exe 2812 Console 1 9,456 KQQBrowser.exe 6804 Console 1 29,972 K
taskkill 杀死进程
C:\Users\ZL>taskkill /F /PID 6804成功: 已终止 PID 为 6804 的进程。C:\Users\ZL>tasklist /FI "IMAGENAME eq QQBrowser.exe"映像名称 PID 会话名 会话# 内存使用========================= ======== ================ =========== ============QQBrowser.exe 2812 Console 1 9,480 K
linux
ps - ef 查看所有进程ps -fT -p <PID> 查看某个进程(PID)的所有线程kill 杀死进程top按大写 H 切换是否显示线程top -H -p <PID> 查看某个进程(PID)的所有线程ps -ef | grep 内容
Java
jps命令查看所有 Java 进程,jps -l jps -v 查看更详细信息 jstack <PID> 查看某个 Java 进程(PID)的所有线程状态jconsole来查看某个 Java 进程中线程的运行情况(图形界面)
jconsole 远程监控配置
需要以如下方式运行你的 java 类
java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=是否安全连接 -Dcom.sun.management.jmxremote.authenticate=是否认证 java类
修改 /etc/hosts 文件将 127.0.0.1 映射至主机名
如果要认证访问,还需要做如下步骤
复制 jmxremote.password 文件
修改 jmxremote.password 和 jmxremote.access 文件的权限为 600 即文件所有者可读写
连接时填入 controlRole(用户名),R&D(密码)
3.4 原理之线程运行
栈与栈帧
Java Virtual Machine Stacks (Java 虚拟机栈)
我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
线程上下文切换(Thread Context Switch)
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
- 线程的 cpu 时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
- 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
- Context Switch 频繁发生会影响性能
3.5 常见方法
| 方法名 | 功能说明 | 注意 | static |
|---|---|---|---|
| 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() | 判断是否被打断 | 不会清除 打断标记 | |
| interrupt() | 打断线程 | 如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除 打断标记 ;如果打断的正在运行的线程,则会设置 打断标记 ;park 的线程被打断,也会设置 打断标记 | |
| isAlive() | 线程是否存活(还没有运行完毕) | ||
| interrupted() | 判断当前线程是否被打断 | 会清除 打断标记 | static |
| currentThread() | 获取当前正在执行的线程 | static | |
| sleep(long n) | 让当前执行的线程休眠n毫秒,休眠时让出 cpu的时间片给其它线程 | static | |
| yield() | 提示线程调度器让出当前线程对CPU的使用 | 主要是为了测试和调试 | static |
3.6 start 与 run
直接调用 run 是在主线程中执行了 run,没有启动新的线程
使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
3.7 sleep 与 yield
sleep
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
3. 睡眠结束后的线程未必会立刻得到执行
4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
运行结果public static void main(String[] args) throws InterruptedException {Runnable runnable = () -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {log.error(e.getLocalizedMessage());}};String name = "not main";Thread thread = new Thread(runnable, name);log.info(name+" state:" + thread.getState());thread.start();log.info(name+" state:" + thread.getState());Thread.sleep(5);log.info(name+" state:" + thread.getState());thread.interrupt();Thread.sleep(5);log.info(name+" state:" + thread.getState());}
20:48:19 [main] com.zhr.thread.TestYeild - not main state:NEW20:48:19 [main] com.zhr.thread.TestYeild - not main state:RUNNABLE20:48:19 [main] com.zhr.thread.TestYeild - not main state:TIMED_WAITING20:48:19 [not main] com.zhr.thread.TestYeild - sleep interrupted20:48:19 [main] com.zhr.thread.TestYeild - not main state:TERMINATED
yield
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
2. 具体的实现依赖于操作系统的任务调度器
线程优先级
线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用Runnable task1 = () -> {int count = 0;for (;;) {System.out.println("---->1 " + count++);}};Runnable task2 = () -> {int count = 0;for (;;) {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();
案例-防止CPU占用100%
sleep实现
在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield或sleep来让出 cpu的使用权给其他程序
可以用wait或条件变量达到类似的效果new Thread(() -> {while (true) {try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}}}).start();
不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
sleep适用于无需锁同步的场景
3.8 join 方法详解
代码
@Slf4jpublic class Test5 {public static void main(String[] args) {AtomicInteger r = new AtomicInteger();log.debug("开始");Thread t1 = new Thread(() -> {log.debug("开始");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}log.debug("结束");r.set(10);});t1.start();log.debug("结果为:{}", r);log.debug("结束");}}
运行结果
14:01:48 [main] com.zhr.thread.Test5 - 开始14:01:48 [main] com.zhr.thread.Test5 - 结果为:014:01:48 [main] com.zhr.thread.Test5 - 结束14:01:48 [Thread-0] com.zhr.thread.Test5 - 开始14:01:49 [Thread-0] com.zhr.thread.Test5 - 结束
分析
因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
而主线程一开始就要打印 r 的结果,所以只能打印出 r=0
解决方法
用 sleep 行不行?为什么?
用 join,加在 t1.start() 之后即可
时间足够
代码
@Slf4jpublic class TestJoin {public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();Thread thread1 = new Thread(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}});Thread thread2 = new Thread(() -> {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}});thread1.start();thread2.start();thread1.join(3000);thread2.join(3000);long end = System.currentTimeMillis();log.info("use = " + (end - start) + "ms");}}
运行结果
23:14:40 [main] com.zhr.thread.TestJoin2 - use = 2015ms
时间不足
//...TimeUnit.SECONDS.sleep(3);TimeUnit.SECONDS.sleep(4);//...thread1.join(1000);thread2.join(1000);//...
运行结果
23:13:24 [main] com.zhr.thread.TestJoin - use = 2016ms
总结:
等待一个或者多个结果,
子线程执行时间小于等待时间,子线程执行完就继续向下运行,main线程等待的时间为子线程实际使用的最长时间
子线程执行时间超过等待时间则,则main线程不再等待子线程,继续向下运行;
3.9 interrupt 方法详解
打断 sleep,wait,join 的线程
join 的底层其实就是wait
这几个方法都会让线程进入阻塞状态
打断 sleep 的线程, 会清空打断状态,以 sleep 为例
@Slf4jpublic class TestInterrupt {private static Object o=new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {Thread t = Thread.currentThread();//打断状态被重置log.debug(" 打断状态: {}, 线程状态: {}", t.isInterrupted(), t.getState());log.debug(e.getLocalizedMessage());}}, "t1");t1.start();//等待t1线程进入睡眠状态TimeUnit.MILLISECONDS.sleep(20);t1.interrupt();log.debug(" 打断状态: {}, 线程状态: {}", t1.isInterrupted(), t1.getState());TimeUnit.MILLISECONDS.sleep(20);log.debug(" 打断状态: {}, 线程状态: {}", t1.isInterrupted(), t1.getState());}}
运行结果
15:54:41.706 [main] DEBUG com.zhr.thread.TestInterrupt - 打断状态: false, 线程状态: TIMED_WAITING15:54:41.706 [t1] DEBUG com.zhr.thread.TestInterrupt - 打断状态: false, 线程状态: RUNNABLE15:54:41.715 [t1] DEBUG com.zhr.thread.TestInterrupt - sleep interrupted15:54:41.762 [main] DEBUG com.zhr.thread.TestInterrupt - 打断状态: false, 线程状态: TERMINATED
打断正常运行的线程
打断正常运行的线程, 不会清空打断状态
public static void main(String[] args) throws InterruptedException {Thread t2 = new Thread(()->{while(true) {Thread current = Thread.currentThread();boolean interrupted = current.isInterrupted();if(interrupted) {log.debug(" 打断状态: {}", interrupted);break;}}}, "t2");t2.start();TimeUnit.MILLISECONDS.sleep(500);t2.interrupt();}
运行结果
10:27:58 [t2] com.zhr.thread.TestInterrupt1 - 打断状态: true
* 模式之 两阶段终止(Two Phase Termination)
在一个线程T1中如何“优雅”终止线程T2?这里的【优雅】指的是给T2一个料理后事的机会。
1.错误思路
- 使用线程对象的stop()方法停止线程
- stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁
- 使用System.exit(int)方法停止线程
- 目的仅是停止一个线程,但这种做法会让整个程序都停止

@Slf4jpublic class 两阶段终止模式 {public static void main(String[] args) throws InterruptedException {MonitorDemo monitorDemo = new MonitorDemo(() -> {System.out.println("获取监控状态");System.out.println();},200);monitorDemo.start();Thread.sleep(3000);monitorDemo.stop();}}@Slf4jclass MonitorDemo {private Thread monitorThread;private Runnable runnable;private int rate;public MonitorDemo(Runnable runnable,int rate) {this.runnable = runnable;this.rate = rate;}public void start() {monitorThread = new Thread(() -> {while (true) {if (monitorThread.isInterrupted()) {break;}try {//睡眠中被打断 打断标记被重置为false需要在catch代码快中充新打断Thread.sleep(rate);//监控线程运行时被打断 下次循环直接退出runnable.run();} catch (InterruptedException e) {e.printStackTrace();//重新设置打断标记monitorThread.interrupt();}}});monitorThread.start();}public void stop() {monitorThread.interrupt();}}
打断 park 线程
LockSupport.park() 的作用:让当前线程停下来
打断 park 线程, 不会清空打断状态
Thread t1 = new Thread(() -> {log.debug("park...");LockSupport.park();log.debug("unpark...");log.debug("打断状态:{}", Thread.currentThread().isInterrupted());}, "t1");t1.start();TimeUnit.MILLISECONDS.sleep(500);t1.interrupt();
运行结果
10:59:24 [t1] com.zhr.thread.TestInterrupt2 - park...10:59:24 [t1] com.zhr.thread.TestInterrupt2 - unpark...10:59:24 [t1] com.zhr.thread.TestInterrupt2 - 打断状态:true
如果打断标记已经是 true, 则 park 会失效
Thread t1 = new Thread(() -> {for (int i = 0; i < 5; i++) {log.debug("park...");LockSupport.park();log.debug("打断状态:{}", Thread.currentThread().isInterrupted());}});t1.start();TimeUnit.SECONDS.sleep(1);log.debug("sleep结束 准备打断...");t1.interrupt();
运行结果
15:15:56 [Thread-0] com.zhr.thread.TestInterrupt3 - park...15:15:57 [main] com.zhr.thread.TestInterrupt3 - sleep结束 准备打断...15:15:57 [Thread-0] com.zhr.thread.TestInterrupt3 - 打断状态:true15:15:57 [Thread-0] com.zhr.thread.TestInterrupt3 - park...15:15:57 [Thread-0] com.zhr.thread.TestInterrupt3 - 打断状态:true15:15:57 [Thread-0] com.zhr.thread.TestInterrupt3 - park...15:15:57 [Thread-0] com.zhr.thread.TestInterrupt3 - 打断状态:true15:15:57 [Thread-0] com.zhr.thread.TestInterrupt3 - park...15:15:57 [Thread-0] com.zhr.thread.TestInterrupt3 - 打断状态:true15:15:57 [Thread-0] com.zhr.thread.TestInterrupt3 - park...15:15:57 [Thread-0] com.zhr.thread.TestInterrupt3 - 打断状态:true
提示 可以使用 Thread.interrupted() 清除打断状态
3.10 不推荐的方法
还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁
| 方法名 | 功能说明 | static |
|---|---|---|
| stop() | 停止线程运行 | |
| suspend() | 挂起(暂停)线程运行 | |
| resume() | 恢复线程运行 |
3.11 主线程与守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守
护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
log.debug("开始运行...");Thread t1 = new Thread(() -> {log.debug("开始运行...");try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}log.debug("运行结束...");}, "daemon");// 设置该线程为守护线程t1.setDaemon(true);t1.start();//如果main线程不sleep,t1线程可能都不会运行TimeUnit.SECONDS.sleep(1);log.debug("运行结束...");
运行结果
15:26:47 [main] com.zhr.thread.TestDaemon - 开始运行...15:26:47 [daemon] com.zhr.thread.TestDaemon - 开始运行...15:26:48 [main] com.zhr.thread.TestDaemon - 运行结束...
注意
- 垃圾回收器线程就是一种守护线程
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求
3.12 线程状态
从 操作系统 层面来描述:五种状态

- 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
- 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
- 【运行状态】指获取了 CPU 时间片运行中的状态
- 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
- 【阻塞状态】
- 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
- 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
- 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
从 Java API 层面来描述: 六种状态
根据 Thread.State 枚举,分为六种状态
NEW线程刚被创建,但是还没有调用 start() 方法RUNNABLE当调用了 start() 方法之后,注意,Java API 层面的RUNNABLE状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(操作系统中,由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)BLOCKED,WAITING,TIMED_WAITING都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节详述TERMINATED当线程代码运行结束
打印状态
@Slf4j(topic = "c.TestState")public class TestState {public static void main(String[] args) throws IOException {Thread t1 = new Thread("t1") {@Overridepublic void run() {log.debug("running...");}};Thread t2 = new Thread("t2") {@Overridepublic void run() {while(true) { // runnable}}};t2.start();Thread t3 = new Thread("t3") {@Overridepublic void run() {log.debug("running...");}};t3.start();Thread t4 = new Thread("t4") {@Overridepublic void run() {synchronized (TestState.class) {try {Thread.sleep(1000000); // timed_waiting} catch (InterruptedException e) {e.printStackTrace();}}}};t4.start();Thread t5 = new Thread("t5") {@Overridepublic void run() {try {t2.join(); // waiting} catch (InterruptedException e) {e.printStackTrace();}}};t5.start();Thread t6 = new Thread("t6") {@Overridepublic void run() {synchronized (TestState.class) { // blockedtry {Thread.sleep(1000000);} catch (InterruptedException e) {e.printStackTrace();}}}};t6.start();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}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());System.in.read();}}
运行结果
18:36:05.682 c.TestState [t3] - running...18:36:06.180 c.TestState [main] - t1 state NEW18:36:06.183 c.TestState [main] - t2 state RUNNABLE18:36:06.183 c.TestState [main] - t3 state TERMINATED18:36:06.183 c.TestState [main] - t4 state TIMED_WAITING18:36:06.183 c.TestState [main] - t5 state WAITING18:36:06.183 c.TestState [main] - t6 state BLOCKED
3.13 烧水泡茶
华罗庚《统筹方法》,给出烧水泡茶的多线程解决方案,提示
用两个线程(两个人协作)模拟烧水泡茶过程
洗好水壶,灌上凉水,放在火上;在等待水开的时间里,洗茶壶、洗茶杯、拿茶叶;等水开了,泡茶喝。

@Slf4jpublic class TestTea {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {work(1, "洗水壶");work(15, "烧开水");}, "t1");Thread t2 = new Thread(() -> {work(1, "洗茶壶");work(2, "洗茶杯");work(1, "拿茶叶");}, "t2");t1.start();t2.start();t1.join();t2.join();log.info("泡茶");}private static void work(long time, String task) {try {TimeUnit.MILLISECONDS.sleep(time * 10);log.info(task + "完成");} catch (InterruptedException e) {e.printStackTrace();}}}
运行结果
22:41:20.301 [t1] com.zhr.thread.TestTea - 洗水壶完成22:41:20.302 [t2] com.zhr.thread.TestTea - 洗茶壶完成22:41:20.340 [t2] com.zhr.thread.TestTea - 洗茶杯完成22:41:20.356 [t2] com.zhr.thread.TestTea - 拿茶叶完成22:41:20.467 [t1] com.zhr.thread.TestTea - 烧开水完成22:41:20.468 [main] com.zhr.thread.TestTea - 泡茶
本章小结
本章的重点在于掌握
- 线程创建
- 线程重要 api,如 start,run,sleep,join,interrupt 等
- 线程状态
- 应用方面
- 异步调用:主线程执行期间,其它线程异步执行耗时操作
- 提高效率:并行计算,缩短运算时间
- 同步等待:join
- 统筹规划:合理使用线程,得到最优效果
- 原理方面
- 线程运行流程:栈、栈帧、上下文切换、程序计数器
- Thread 两种创建方式 的源码
- 模式方面
- 终止模式之两阶段终止
