Java高并发编程-(一)线程与进程、并行并发、同步异步概念
Java 高并发编程 一 线程与进程、并行并发、同步异步概念·
一、线程与进程、并行并发、同步异步概念·
1、进程与进程·
进程: 资源分配的最小单位进程是线程的容器, 一个进程中包含多个线程, 真正执行任务的是线程线程: 资源调度的最小单位
进程·
- 程序由指令和数据组成,但是这些 指令要运行,数据要读写,就必须将指令加载到cpu,数据加载至内存。在指令运行过程中还需要用到磁盘,网络等设备,进程就是用来加载指令,管理内存,管理IO的
- 当一个指令被运行,从磁盘加载这个程序的代码到内存,这时候就开启了一个进程
- 进程就可以视为程序的一个实例,大部分程序都可以运行多个实例进程(例如记事本,浏览器等),部分只可以运行一个实例进程(例如360安全卫士)
线程·
- 一个进程之内可以分为多个线程。
- 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
- Java 中,线程作为资源的最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作为线程的容器。
二者对比·
- 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
- 进程拥有共享的资源,如内存空间等,供其内部的线程共享; 进程间通信较为复杂
- 同一台计算机的进程通信称为 IPC(Inter-process communication)
- 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
- 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
- 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
2、 并行与并发·
并发: 在单核CPU下, 一定是并发执行的, 也就是在同一个时间段内一起执行. 实际还是串行执行, CPU的时间片切换非常快, 给人一种同时运行的感觉。
并行: 在多核CPU下, 能真正意义上实现并行执行, 在同一个时刻, 多个线程同时执行; 比如说2核cpu, 同时执行4个线程. 理论上同时可以有2个线程是并行执行的. 此时还是存在并发, 因为2个cpu也会同时切换不同的线程执行任务罢了
并发 (concurrent)·
- 微观串行, 宏观并行
- 在单核 cpu下,线程实际还是串行执行的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于cpu 在线程间(时间片很短)的切换非常快,给人的 感觉是同时运行的 。一般会将这种线程轮流使用 CPU的做法称为并发(concurrent)
- 将线程轮流使用cput称为并发(concurrent)
并行·
多核 cpu下,每个核(core) 都可以调度运行线程,这时候线程可以是并行的,不同的线程同时使用不同的cpu在执行。
二者对比·
- 引用 Rob Pike 的一段描述:
- 并发(concurrent): 是同一时间应对(dealing with)多件事情的能力
- 并行(parallel): 是同一时间动手做(doing)多件事情的能力
例子l
- 家庭主妇做饭、打扫卫生、给孩子喂奶,她一个人轮流交替做这多件事,这时就是并发
- 家庭主妇雇了个保姆,她们一起这些事,这时既有并发,也有并行(这时会产生竞争,例如锅只有一口,一个人用锅时,另一个人就得等待)
- 雇了3个保姆,一个专做饭、一个专打扫卫生、一个专喂奶,互不干扰,这时是 并行
3、同步和异步·
以调用方的角度讲
- 如果需要等待结果返回才能继续运行的话就是同步
- 如果不需要等待就是异步
注意:同步在多线程中还有另一层意思,是让多个线程步调一致
1 设计·
- 多线程可以让方法执行变为异步的(即不要巴巴干等着)比如说读取磁盘文件时,假设读取操作花费了 5 秒钟,如果没有线程调度机制,这5秒cpu什么都做不了,其它代码都得暂停
2 结论·
- 比如在项目中,视频文件需要转换格式等操作比较费时,这时开一个新线程处理视频转换,避免阻塞主线程
- tomcat 的异步 servlet 也是类似的目的,让用户线程处理耗时较长的操作,避免阻塞 tomcat 的工作线程
- UI 程序中,开线程进行其他操作,避免阻塞 UI 线程
二、线程的创建 (重点)·
1、创建一个线程(非主线程)·
1、通过继承Thread创建线程·
1 | public class CreateThread { |
使用继承方式的好处是,在run()方法内获取当前线程直接使用this就可以了,无须使用Thread.currentThread()方法;不好的地方是Java不支持多继承,如果继承了Thread类,那么就不能再继承其他类。另外任务与代码没有分离,当多个线程执行一样的任务时需要多份任务代码
2、使用Runnable配合Thread (推荐)·
1 | public class Test2 { |
输出结果
17:30:24.309 [main] INFO create.thread - running 17:30:24.309 [createThread] INFO create.thread - runing
或者
1 | public class CreateThread2 { |
- 通过实现Runnable接口,并且实现run()方法。在创建线程时作为参数传入该类的实例即可
方法二的简化:使用lambda表达式简化操作
- 当一个接口带有
@FunctionalInterface
注解时,是可以使用lambda来简化操作的 - 所以方法二中的代码可以被简化为
1 | public class Test2 { |
原理之 Thread 与 Runnable 的关系·
- 分析 Thread 的源码,理清它与 Runnable 的关系
- runnable实际走的还是thread的Run方法
小结
- 继承Thread方式: 是把线程和任务合并在了一起
- 实现Runnable方式: 是把线程和任务分开了
- 用 Runnable 更容易与线程池等高级 API 配合 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
3、使用FutureTask与Thread结合·
使用FutureTask可以用泛型指定线程的返回值类型(Runnable的run方法没有返回值)
1 | public class Test3 { |
或
1 | public class UseFutureTask { |
4、使用线程池来创建线程·
1 | /** |
总结
- 使用 继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过set方法设置参数或者通过构造函数进行传递,而如果使用Runnable方式,则只能使用主线程里面被声明为final的变量。不好的地方是Java不支持多继承,如果继承了Thread类,那么子类不能再继承其他类,而Runable则没有这个限制。前两种方式都没办法拿到任务的返回结果,但是Futuretask方式可以
- 开发中一般使用线程池的方式
2. 查看进程线程的方法·
三、线程运行原理 (重点)·
1、虚拟机栈与栈帧·
- 虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,是属于线程的私有的。当Java中使用多线程时,每个线程都会维护它自己的栈帧!每个线程只能有一个活动栈帧(在栈顶),对应着当前正在执行的那个方法
栈帧图解地址https://www.bilibili.com/video/BV16J411h7Rd?p=21&spm_id_from=pageDriver
2、线程上下文切换(Thread Context Switch)·
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程
- 线程的 cpu 时间片用完(每个线程轮流执行,看前面并行的概念)
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
当Thread Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
- 线程的状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
- Context Switch 频繁发生会影响性能
3、Thread的常见方法·
方法名 | 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() | 判断是否被打 断, | 不会清除 打断标记 | |
isAlive() | 线程是否存活 (还没有运行完 毕) | ||
interrupt() | 打断线程 | 如果被打断线程正在 sleep,wait,join 会导致被打断 的线程抛出 InterruptedException,并清除 打断标 记 ;如果打断的正在运行的线程,则会设置 打断标 记 ;park 的线程被打断,也会设置 打断标记 | |
interrupted() | static | 判断当前线程是 否被打断 | 会清除 打断标记 |
currentThread() | static | 获取当前正在执 行的线程 | |
sleep(long n) | static | 让当前执行的线 程休眠n毫秒, 休眠时让出 cpu 的时间片给其它 线程 | |
yield() | static | 提示线程调度器 让出当前线程对 CPU的使用 | 主要是为了测试和调试 |
3.1、调用start 与 run方法的区别·
调用start()方法·
1 | public static void main(String[] args) { |
- 输出:程序在t1 线程运行, run()方法里面内容的调用是异步的代码
11:59:40.711 [main] DEBUG com.concurrent.test.Test4 - 主线程 11:59:40.711 [新建线程] DEBUG com.concurrent.test.Test4 - 我是一个新建的线程正在运行中 11:59:40.732 [新建线程] DEBUG com.concurrent.test.FileReader - read [test] start … 11:59:40.735 [新建线程] DEBUG com.concurrent.test.FileReader - read [test] end … cost: 3 ms
调用run()方法·
1 | public static void main(String[] args) { |
将上面代码的thread.start();改为 thread.run();输出结果如下:
21:30:00.678 [main] INFO create.thread - 我是一个新建的线程正在运行中 21:30:00.689 [main] INFO create.thread - 主线程
程序仍在 main 线程运行, run()方法里面内容的调用还是同步的
小结·
- 直接调用 run() 是在主线程中执行了 run(),没有启动新的线程
- 使用 start() 是启动新的线程,通过新的线程间接执行 run()方法中的代码
3.2、 sleep 与 yield·
sleep方法·
- 调用 sleep() 会让当前线程从 Running(运行状态) 进入 Timed Waiting 状态(阻塞)
1 | public static void main(String[] args) { |
21:45:01.276 [main] DEBUG create.thread - t1 state: RUNNABLE 21:45:01.784 [main] DEBUG create.thread - t1 state: TIMED_WAITING
- 其它线程可以使用interrupt 方法打断正在睡眠的线程,那么被打断的线程这时就会抛出 InterruptedException异常【注意:这里打断的是正在休眠的线程,而不是其它状态的线程】
1 | public static void main(String[] args) throws InterruptedException { |
23:18:31.756 [t1] DEBUG create.thread - enter sleep… 23:18:32.765 [main] DEBUG create.thread - interrupt… 23:18:32.766 [t1] DEBUG create.thread - wake up… java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.fei.CreateThread$1.run(CreateThread.java:22)
- 睡眠结束后的线程未必会立刻得到执行 (需要分配到cpu时间片)
- 建议用 TimeUnit 的 sleep() 代替 Thread 的 sleep()来获得更好的可读性
1 | public static void main(String[] args) throws InterruptedException { |
yield方法·
- 调用 yield 会让当前线程从Running 进入 Runnable 就绪状态,然后调度执行其它线程
- 具体的实现依赖于操作系统的任务调度器(就是可能没有其它的线程正在执行,虽然调用了yield方法,但是也没有用)
小结·
- yield使cpu调用其它线程,但是cpu可能会再分配时间片给该线程;而sleep需要等过了休眠时间之后才有可能被分配cpu时间片
3.3、线程优先级·
- 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它, 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
- **优先级1-10,越大优先级越高,**
MIN_PRIORITY
最小优先级NORM_PRIORITY
默认优先级MAX_PRIORITY
最大优先级
1 | thread1.setPriority(Thread.MAX_PRIORITY); //设置为优先级最高 |
1 | public static void main(String[] args) { |
3.4、 join方法·
- 在主线程中调用t1.join,则主线程会等待t1线程执行完之后再继续执行
1 | static int r = 0; |
16:33:54.523 [main] DEBUG create.thread - 开始 16:33:54.526 [Thread-1] DEBUG create.thread - 开始 16:33:54.528 [main] DEBUG create.thread - 结果为:0 16:33:54.529 [main] DEBUG create.thread - 结束 16:33:55.532 [Thread-1] DEBUG create.thread - 结束
分析
- 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
- 而主线程一开始就要打印 r 的结果,所以只能打印出 r=0
解决方法
- 用join,加在t1.start()之后
1 | static int r = 0; |
16:31:28.007 [main] DEBUG create.thread - 开始 16:31:28.010 [Thread-1] DEBUG create.thread - 开始 16:31:29.023 [Thread-1] DEBUG create.thread - 结束 16:31:29.025 [main] DEBUG create.thread - 结果为:10 16:31:29.025 [main] DEBUG create.thread - 结束
应用同步案例·
以调用方角度来讲,如果
- 需要等待结果返回,才能继续运行就是同步
- 不需要等待结果返回,就能继续运行就是异步
1 | static int r1 = 0; |
20:07:40.004 [main] DEBUG create.thread - r1: 10 r2: 20 cost: 2002
分析如下
- 第一个 join:等待 t1 时, t2 并没有停止, 而在运行
- 第二个 join:1s 后, 执行到此, t2 也运行了 1s, 因此也只需再等待 1s
有时效的 join·
等够时间
1 | static int r1 = 0; |
20:15:36.841 [main] DEBUG create.thread - r1: 10 r2: 0 cost: 1007
没等够时间
1 | static int r1 = 0; |
20:16:11.589 [main] DEBUG create.thread - r1: 0 r2: 0 cost: 1506
3.5 interrupt 方法详解·
该方法用于打断 sleep,wait,join的线程, 在阻塞期间cpu不会分配给时间片
- 先了解一些interrupt()方法的相关知识:https://www.cnblogs.com/noteless/p/10372826.html#0
- 如果一个线程在在运行中被打断,打断标记会被置为true,但程序不会停止
- 如果是打断因sleep wait join方法而被阻塞的线程,会将打断标记置为false
1 | public static void main(String[] args) throws InterruptedException { |
20:24:35.661 [Thread-1] DEBUG create.thread - sleep… 20:24:36.670 [main] DEBUG create.thread - iterrupt… 20:24:36.670 [main] DEBUG create.thread - false java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.fei.CreateThread.lambda$main$0(CreateThread.java:21) at java.lang.Thread.run(Thread.java:748)
打断正常运行的线程·
被打断的线程可以自己决定是否停止线程,打断状态可以用来停止线程
1 | public static void main(String[] args) throws InterruptedException { |
20:37:45.779 [t2] DEBUG create.thread - 打断状态: true
3.6、 终止模式之两阶段终止模式·
当我们在执行线程一时,想要终止线程二,这是就需要使用interrupt方法来优雅的停止线程二。
如下所示:那么线程的isInterrupted()方法可以取得线程的打断标记
- 如果线程在睡眠sleep期间被打断,打断标记是不会变的,为false,但是sleep期间被打断会抛出异常,我们据此手动设置打断标记为true;
- 如果是在程序正常运行期间被打断的,那么打断标记就被自动设置为true。处理好这两种情况那我们就可以放心地来料理后事啦!
下图①就是正常运行打断, ②是在睡眠中被打断
1 |
|
3.7 打断 park 线程·
当打断标记为true往下执行打断 park 线程, 不会清空打断状态
1 | public static void main(String[] args) throws InterruptedException { |
20:59:08.548 [t1] DEBUG create.thread - park… 20:59:09.555 [t1] DEBUG create.thread - unpark… 20:59:09.557 [t1] DEBUG create.thread - 打断状态:true
如果打断标记已经是 true, 则 park 会失效
1 | public static void main(String[] args) throws InterruptedException { |
21:06:21.054 [Thread-1] DEBUG create.thread - park… 21:06:22.068 [Thread-1] DEBUG create.thread - 打断状态:true 21:06:22.068 [Thread-1] DEBUG create.thread - park… 21:06:22.068 [Thread-1] DEBUG create.thread - 打断状态:true 21:06:22.068 [Thread-1] DEBUG create.thread - park… 21:06:22.068 [Thread-1] DEBUG create.thread - 打断状态:true 21:06:22.068 [Thread-1] DEBUG create.thread - park… 21:06:22.068 [Thread-1] DEBUG create.thread - 打断状态:true 21:06:22.068 [Thread-1] DEBUG create.thread - park… 21:06:22.068 [Thread-1] DEBUG create.thread - 打断状态:true
3.8、sleep,yiled,wait,join 对比·
补充: sleep,join,yield,interrupted是Thread类中的方法 wait/notify是object中的方法 sleep 不释放锁、释放cpu join 释放锁、抢占cpu yiled 不释放锁、释放cpu wait 释放锁、释放cpu
还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁
方法名 | static | 功能说明 |
---|---|---|
stop() | 停止线程运行 | |
suspend() | 挂起(暂停)线程运行 | |
resume() | 恢复线程运行 |
3.9 主线程与守护线程·
当Java进程中有多个线程在执行时,只有当所有非守护线程都执行完毕后,Java进程才会结束。但当非守护线程全部执行完毕后,守护线程无论是否执行完毕,也会一同结束。
1 | public static void main(String[] args) throws InterruptedException { |
21:19:40.800 [main] DEBUG create.thread - 开始运行… 21:19:40.803 [daemon] DEBUG create.thread - 开始运行… 21:19:41.805 [main] DEBUG create.thread - 运行结束…
注意
- 垃圾回收器线程就是一种守护线程
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求
四、 线程状态·
五种状态·
在操作系统的层面上
- 初始状态,仅仅是在语言层面上创建了线程对象,即
Thead thread = new Thead();
,还未与操作系统线程关联 - 可运行状态,也称就绪状态,指该线程已经被创建,与操作系统相关联,等待cpu给它分配时间片就可运行
- 运行状态,指线程获取了CPU时间片,正在运行
- 当CPU时间片用完,线程会转换至【可运行状态】,等待 CPU再次分配时间片,会导致我们前面讲到的上下文切换
- 阻塞状态
- 如果调用了阻塞API,如BIO读写文件,那么线程实际上不会用到CPU,不会分配CPU时间片,会导致上下文切换,进入【阻塞状态】
- 等待BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,只要操作系统一直不唤醒线程,调度器就一直不会考虑调度它们,CPU就一直不会分配时间片
- 终止状态,表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
六种状态·
- 这是从 Java API 层面来描述的
- 根据Thread.State 枚举,分为六种状态
- NEW (新建状态) 线程刚被创建,但是还没有调用 start() 方法
- RUNNABLE (运行状态) 当调用了 start() 方法之后,注意,Java API 层面的RUNNABLE 状态涵盖了操作系统层面的 【就绪状态】、【运行中状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为 是可运行)
- BLOCKED (阻塞状态) , WAITING (等待状态) , TIMED_WAITING(定时等待状态) 都是 Java API 层面对【阻塞状态】的细分,如sleep就位TIMED_WAITING, join为WAITING状态。后面会在状态转换一节详述。
- TERMINATED (结束状态) 当线程代码运行结束
1 | package com.fei; |
21:40:36.814 [t3] DEBUG create.thread - running… 21:40:37.327 [main] DEBUG create.thread - t1 state NEW 21:40:37.328 [main] DEBUG create.thread - t2 state RUNNABLE 21:40:37.328 [main] DEBUG create.thread - t3 state TERMINATED 21:40:37.328 [main] DEBUG create.thread - t4 state TIMED_WAITING 21:40:37.328 [main] DEBUG create.thread - t5 state WAITING 21:40:37.328 [main] DEBUG create.thread - t6 state BLOCKED
习题·
阅读华罗庚《统筹方法》,给出烧水泡茶的多线程解决方案,提示
- 参考图二,用两个线程(两个人协作)模拟烧水泡茶过程
- 文中办法乙、丙都相当于任务串行
- 而图一相当于启动了 4 个线程,有点浪费
- 用 sleep(n) 模拟洗茶壶、洗水壶等耗费的时间
附:华罗庚《统筹方法》
统筹方法,是一种安排工作进程的数学方法。它的实用范围极广泛,在企业管理和基本建设中,以及关系复 杂的科研项目的组织与管理中,都可以应用。怎样应用呢?主要是把工序安排好。比如,想泡壶茶喝。当时的情况是:开水没有;水壶要洗,茶壶、茶杯要洗;火已生了,茶叶也有了。怎么 办?
办法甲:洗好水壶,灌上凉水,放在火上;在等待水开的时间里,洗茶壶、洗茶杯、拿茶叶;等水开 了,泡茶喝。 办法乙:先做好一些准备工作,洗水壶,洗茶壶茶杯,拿茶叶;一切就绪,灌水烧水;坐待水开了,泡 茶喝。 办法丙:洗净水壶,灌上凉水,放在火上,坐待水开;水开了之后,急急忙忙找茶叶,洗茶壶茶杯,泡 茶喝。
哪一种办法省时间?我们能一眼看出,第一种办法好,后两种办法都窝了工。
这是小事,但这是引子,可以引出生产管理等方面有用的方法来。
水壶不洗,不能烧开水,因而洗水壶是烧开水的前提。没开水、没茶叶、不洗茶壶茶杯,就不能泡茶,因而 这些又是泡茶的前提。它们的相互关系,可以用下边的箭头图来表示:
从这个图上可以一眼看出,办法甲总共要16分钟(而办法乙、丙需要20分钟)。如果要缩短工时、提高工作 效率,应当主要抓烧开水这个环节,而不是抓拿茶叶等环节。同时,洗茶壶茶杯、拿茶叶总共不过4分钟,大 可利用“等水开”的时间来做。
是的,这好像是废话,卑之无甚高论。有如走路要用两条腿走,吃饭要一口一口吃,这些道理谁都懂得。但 稍有变化,临事而迷的情况,常常是存在的。在近代工业的错综复杂的工艺过程中,往往就不是像泡茶喝这 么简单了。任务多了,几百几千,甚至有好几万个任务。关系多了,错综复杂,千头万绪,往往出现“万事俱 备,只欠东风”的情况。由于一两个零件没完成,耽误了一台复杂机器的出厂时间。或往往因为抓的不是关 键,连夜三班,急急忙忙,完成这一环节之后,还得等待旁的环节才能装配。
洗茶壶,洗茶杯,拿茶叶,或先或后,关系不大,而且同是一个人的活儿,因而可以合并成为: 看来这是“小题大做”,但在工作环节太多的时候,这样做就非常必要了。 这里讲的主要是时间方面的事,但在具体生产实践中,还有其他方面的许多事。这种方法虽然不一定能直接 解决所有问题,但是,我们利用这种方法来考虑问题,也是不无裨益的。
本章小结·
本章的重点在于掌握
- 线程创建
- 线程重要 api,如 start,run,sleep,join,interrupt 等
- 线程状态
- 应用方面
- 异步调用:主线程执行期间,其它线程异步执行耗时操作
- 提高效率:并行计算,缩短运算时间 同步等待:join
- 统筹规划:合理使用线程,得到最优效果
- 原理方面
- 线程运行流程:栈、栈帧、上下文切换、程序计数器
- Thread 两种创建方式 的源码
- 模式方面
- 终止模式之两阶段终止