1. 基礎(chǔ)知識點回顧
1.1 并發(fā)和并行
并發(fā)(Concurrent):系統(tǒng)只有一個CPU,多個線程在操作時,在一段時間內(nèi)只有一個線程在運行,其他線程處于掛起狀態(tài)。
并發(fā)編程的本質(zhì):充分利用CPU的資源
并行(Parallel):系統(tǒng)有一個以上CPU時,多個線程可以同時執(zhí)行
* 應(yīng)用之異步調(diào)用
從方法調(diào)用的角度:
- 需要等待結(jié)果返回,才能繼續(xù)運動就是同步
- 不需要等待結(jié)果返回,就能繼續(xù)運行就是異步
注意:同步在多線程中,還有另外一層意思,讓多個線程步調(diào)一致
1) 設(shè)計
多線程可以讓方法執(zhí)行變?yōu)楫惒剑床灰砂桶偷戎热缱x取磁盤文件時,假設(shè)讀取操作花費了5秒,如果沒有線程調(diào)度機制,這五秒調(diào)用者什么都做不了,其他代碼都得暫停
2) 結(jié)論
- 比如在項目中視頻文件需要轉(zhuǎn)換格式等操作比較費時時,這時開一個新線程處理視頻格式轉(zhuǎn)換,避免阻塞主線程
- UI程序中,開線程進行其他操作,避免阻塞UI線程
方法三:FutureTask 配合 Thread
FutureTask 可以接受 Callable類型參數(shù),用來處理有返回結(jié)果的情況
//創(chuàng)建任務(wù)對象
FutureTask<Integer> task3 = new FutureTask<>(()->{
log.debug("hello");
return 100;
});
new Thread(task3, "task3").start();
//主線程阻塞,等待task3執(zhí)行完畢
Integer result = task3.get();
3.4 線程運行原理
棧與棧幀
- 每個棧由多個棧幀組成,對應(yīng)著每次方法調(diào)用所占用的內(nèi)存
- 每個線程只能有一個活動棧幀,對應(yīng)著當前正在執(zhí)行的那個方法
線程上下文切換
因為以下一些原因?qū)е翪PU不再執(zhí)行當前線程,轉(zhuǎn)而執(zhí)行另一個線程的代碼
- 線程的CPU時間片用完
- 垃圾回收
- 有更高優(yōu)先級的線程需要運行
- 線程自己調(diào)用了sleep ,yield, wait, join, park, synchronized, lock等方法
當發(fā)生上下文切換時,需要由操作系統(tǒng)保存當前線程的狀態(tài),并恢復另一個線程的狀態(tài),Java中對應(yīng)的概念就是程序計數(shù)器,它的作用就是記住下一條jvm指令的執(zhí)行地址,是線程私有的
- 狀態(tài)包括程序計數(shù)器、虛擬機棧中每個棧幀的信息,如局部變量表,操作數(shù)棧,返回地址等
- 上下文切換頻繁發(fā)生會影響性能
3.5 常見方法
| 方法名 | 功能說明 | 注意 |
|---|---|---|
| start() | 啟動一個新的線程 | start()方法只是讓線程進入就緒狀態(tài),里面的代碼不一定立刻運行(CPU時間片還沒分給它)。每個線程對象的start 方法只能調(diào)用一次,調(diào)用多次會出現(xiàn)IllegalThreadStateException |
| run() | 新線程啟動后調(diào)用的方法 | |
| join() | 等待線程運行結(jié)束 | 用在兩個線程通信時 |
| join(long n) | 等待線程運行結(jié)束,最多等待n毫秒 | |
| getId() | 獲取線程id | id唯一 |
| getName() | 獲取線程名 | |
| setName(String) | 修改線程名 | |
| getPriority() | 獲取線程優(yōu)先級 | |
| setPriority() | 修改線程優(yōu)先級 | java中規(guī)定線程優(yōu)先級是1~10的整數(shù),較大的優(yōu)先級能提高該線程被CPU調(diào)度的幾率 |
| getState() | 獲取線程狀態(tài) | java中線程狀態(tài)是由6個enum表示,分別為:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING |
| isInterrupted() | 判斷是否打斷 | 不會清除打斷標記
|
| isAlive() | 線程是否存活(還沒有運行完) | |
| interrupt() | 打斷線程 | 如果被打斷線程正在sleep, wait, join 會導致被打斷的線程拋出InterruptedException,并清除 打斷標記;如果打斷正在運行的程序,則會設(shè)置打斷標記;park線程被打斷,也會設(shè)置打斷標記
|
| interrupted() | 靜態(tài)方法,判斷當前線程是否被打斷 | 會清除打斷標記
|
| sleep(long n) | 靜態(tài)方法,讓當前執(zhí)行線程休眠n毫秒,休眠時讓出CPU時間片給其他線程 | |
| yield() | 靜態(tài)方法,提示線程調(diào)度器讓出當前線程對CPU的使用 | 主要是為了測試和調(diào)試 |
3.7 sleep 與yield
sleep
1.調(diào)用sleep 會讓當前線程從RUNNING 進入 Timed_Waiting狀態(tài)
- 其他線程可以使用interrupt 方法打斷正在睡眠的線程,這時sleep 方法會拋出InterruptedException.
- 睡眠結(jié)束后的線程未必會立刻得到執(zhí)行
- 建議用TimeUnit 的sleep 代替Thread 的sleep 來獲得更好的可讀性
yield
- 調(diào)用yield 會讓當前線程從RUNNING 進入 RUNNABLE 狀態(tài),然后調(diào)度執(zhí)行其他同優(yōu)先級的線程。如果這段時間沒有同優(yōu)先級的線程,那么不能保證讓當前線程暫停的效果
- 具體的實現(xiàn)依賴于操作系統(tǒng)的任務(wù)調(diào)度
2.2 線程優(yōu)先級
- 線程優(yōu)先級會提示調(diào)度器優(yōu)先調(diào)度該線程,但僅僅是一個提示,調(diào)度器可以忽略
- 如果CPU比較忙,那么優(yōu)先級高的線程就會獲得更多的時間片,但CPU閑下來,優(yōu)先級幾乎沒用
3.8 join 方法詳解
為什么需要join
下面的代碼執(zhí)行,打印的r是什么?
public class JoinTest {
static int r =0;
public static void main(String[] args) {
test();
}
private static void test() {
Thread t = new Thread(()->{
System.out.println(Thread.currentThread().getName() + " start ...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
System.out.println(Thread.currentThread().getName() + " end ...");
});
t.start();
System.out.println(Thread.currentThread().getName() + " result is " + r);
}
}
執(zhí)行結(jié)果:
main result is 0
Thread-0 start ...
Thread-0 end ...
分析:
- 因為主線程和線程t是并行執(zhí)行的,t線程需要1秒之后才能計算出r=10;
- 而主線程一開始就要打印結(jié)果,所以只能打印r = 0;
解決方法
- 讓主線程睡一會,用sleep 行不行?為什么?
- 用join,加在 t.start之后即可
public class JoinTest {
static int r =0;
public static void main(String[] args) {
test();
}
private static void test() {
Thread t = new Thread(()->{
System.out.println(Thread.currentThread().getName() + " start ...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
System.out.println(Thread.currentThread().getName() + " end ...");
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " result is " + r);
}
}
執(zhí)行結(jié)果:
Thread-0 start ...
Thread-0 end ...
main result is 10
join 方法源碼
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
* 應(yīng)用之同步(案例1)
以調(diào)用方角度來講,如果
- 需要等待結(jié)果返回,才能繼續(xù)運行就是同步
- 不需要等待結(jié)果返回,就能繼續(xù)運行就是異步
public class JoinTest {
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) {
test2();
}
private static void test2() {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r1 = 10;
System.out.println(Thread.currentThread().getName() + " end ...");
});
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r2 = 20;
System.out.println(Thread.currentThread().getName() + " end ...");
});
t1.start();
t2.start();
long start = System.currentTimeMillis();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("r1 is "+ r1 + " ; r2 is " + r2 + " ; time is " + (end - start)); //注意這個時間差值
}
}
注意end - start 的時間差值
執(zhí)行結(jié)果:
Thread-0 start ...
Thread-1 start ...
Thread-0 end ...
Thread-1 end ...
r1 is 10 ; r2 is 20 ; time is 2002
分析如下:
- 第一個join:等待t1 時,t2并沒有停止,而在運行
- 第二個join: 1s后,執(zhí)行到此,t2也運行了 1s,因此只需再等待1s。
如果顛倒兩個join 呢?
最終輸出都是一樣的。
注意 :如果線程提前結(jié)束,join 也不必繼續(xù)等待。
有時效的join(long n)
public class JoinTest {
static int r = 0;
public static void main(String[] args) {
test3();
}
private static void test3() {
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
System.out.println(Thread.currentThread().getName() + " end ...");
});
t.start();
try {
t.join(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " result is " + r);
}
}
執(zhí)行結(jié)果:
Thread-0 start ...
main result is 0
Thread-0 end ...
2.4 interrupt 方法詳解
打斷 sleep, wait , join 的線程
打斷sleep 的線程,會清空打斷狀態(tài),以sleep為例
public class SleepTest {
public static void main(String[] args) {
Thread t = new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
System.out.println("interrupt is " + t.isInterrupted());
}
}
執(zhí)行結(jié)果:
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.lily.base.SleepTest.lambda0(SleepTest.java:9)
at java.lang.Thread.run(Thread.java:748)
interrupt is false
public class SleepTest {
public static void main(String[] args) {
Thread t = new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
t.interrupt();
System.out.println("interrupt is " + t.isInterrupted()); //還沒有睡眠
}
}
執(zhí)行結(jié)果:
interrupt is true(還沒有睡眠打斷的是正常的線程)
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.lily.base.SleepTest.lambda0(SleepTest.java:9)
at java.lang.Thread.run(Thread.java:748)
打斷正常的線程
打斷正常運行的線程,不會清空打斷標記
public class InterruptTest {
public static void main(String[] args) {
Thread t = new Thread(()->{
while (true) {
}
});
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
調(diào)用interrupt()方法打斷仍然繼續(xù)運行,但是此時 打斷標記為真,可根據(jù)該字段結(jié)束線程
public class InterruptTest {
public static void main(String[] args) {
Thread t = new Thread(()->{
while (true) {
if (Thread.currentThread().isInterrupted()){
break;
}
}
});
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
由此發(fā)現(xiàn)打斷線程可以實現(xiàn)停止線程。
打斷park線程
打斷park 線程,不會清除打斷標記
public class ParkInterruptTest {
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println("park...");
LockSupport.park();
System.out.println("unpark...");
System.out.println("打斷狀態(tài) " + Thread.currentThread().isInterrupted());
});
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
執(zhí)行結(jié)果:
park...
unpark...
打斷狀態(tài) true
如果打斷標記已經(jīng)是true ,則park 會失效。
public class ParkInterruptTest {
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println("park...");
LockSupport.park();
System.out.println("unpark...");
System.out.println("打斷狀態(tài) " + Thread.currentThread().isInterrupted());
LockSupport.park();
System.out.println("unpark 1...");
});
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
執(zhí)行結(jié)果:
park...
unpark...
打斷狀態(tài) true
unpark 1...
2.5 不推薦方法
有一些不推薦使用的方法,方法已過時,容易破壞同步代碼塊,造成死鎖
| 方法名 | static | 功能說明 |
|---|---|---|
| stop() | 停止線程運行 | |
| suspend() | 掛起(暫停)線程運行 | |
| resume | 恢復線程運行 |
3. 主線程和守護線程
默認情況下,java進程需要等待所有 線程都運行結(jié)束,才會結(jié)束。有一種特殊的線程叫守護線程。只要其他非守護線程運行結(jié)束了,即使守護線程的代碼沒有執(zhí)行完,也會強制結(jié)束
public class DeamonTest {
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println(Thread.currentThread().getName() + " start");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " end");
});
t.setDaemon(true);
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " end");
}
}
執(zhí)行結(jié)果:
Thread-0 start
main end
注意:垃圾回收器就是一種守護線程
4. 線程五種狀態(tài)
從操作系統(tǒng)層面描述

- 初始狀態(tài):僅是在語言層面創(chuàng)建了線程對象,還未與操作系統(tǒng)線程關(guān)聯(lián)
- 可運行狀態(tài):指該線程已經(jīng)被創(chuàng)建(與操作系統(tǒng)線程關(guān)聯(lián)),可以由cpu調(diào)度執(zhí)行
- 運行狀態(tài):指獲取了CPU時間片運行中的狀態(tài)
- 當CPU時間片用完,會從
運行狀態(tài)轉(zhuǎn)換至可運行狀態(tài),會導致線程上下文切換
- 當CPU時間片用完,會從
- 阻塞狀態(tài)
- 如果調(diào)用了阻塞API,如BIO讀寫文件,這時該線程實際不會用到CPU,會導致線程上下文切換,進入
阻塞狀態(tài) - 等BIO操作完,會由操作系統(tǒng)喚醒阻塞的線程,轉(zhuǎn)換至
可運行狀態(tài)
- 如果調(diào)用了阻塞API,如BIO讀寫文件,這時該線程實際不會用到CPU,會導致線程上下文切換,進入
- 終止狀態(tài):表示線程已經(jīng)執(zhí)行完畢,生命周期已經(jīng)結(jié)束,不會再轉(zhuǎn)換為其他狀態(tài)
5. 線程六種狀態(tài)
這時從JAVA api層面描述的
根據(jù)Thread.State 枚舉,分為六種狀態(tài)

源碼:
public enum State {
NEW,
/**
* 可運行狀態(tài)。線程對象調(diào)用start()方法
* 如果處于Runnable的線程調(diào)用yield()讓出JVM資源,那么就會進入New狀態(tài)和其他New狀態(tài)
* 線程進行競爭重新進入Runnable
*/
RUNNABLE,
/**
* 阻塞狀態(tài)。阻塞線程不釋放當前占有的系統(tǒng)資源,
* 當sleep()結(jié)束或者join()等待其他線程來到,當前線程進入Runnable狀態(tài)等待JVM分配資
* 源。
* 線程對象調(diào)用sleep()或者join()方法
*/
BLOCKED,
/**
* 等待狀態(tài)。
*
* {@link Object#wait() Object.wait} with no timeout
* {@link #join() Thread.join} with no timeout
* {@link LockSupport#park() LockSupport.park}
*
*一個線程已經(jīng)調(diào)用了Object.wait(),會等待另一個線程調(diào)用Object.notify()或Object.notify()
*一個已經(jīng)調(diào)用Thread.join()的線程將等待這個指定線程終止
*/
WAITING,
/**
* 定時等待狀態(tài):
* 當線程進入Runnable狀態(tài),但還沒有開始運行的時候,此時發(fā)現(xiàn)需要的資源處于同步狀態(tài)
* synchronized,這個時候線程將會進入Timed_waiting,JVM會使用隊列對這些線程進行控
* 制,是這樣狀態(tài)的線程會先得到JVM資源進行執(zhí)行進入Waiting
* {@link #sleep Thread.sleep}
* {@link Object#wait(long) Object.wait} with timeout
* {@link #join(long) Thread.join} with timeout
* {@link LockSupport#parkNanos LockSupport.parkNanos}
* {@link LockSupport#parkUntil LockSupport.parkUntil}
*
*/
TIMED_WAITING,
TERMINATED;
}
- NEW : 線程剛被創(chuàng)建,但是還沒有調(diào)用start()方法
- RUNNABLE: 當調(diào)用了start() 方法之后,注意,java API層面的RUNNABLE 狀態(tài)涵蓋了操作系統(tǒng)層面的
可運行狀態(tài)、運行狀態(tài)和阻塞狀態(tài)(由于BIO導致的線程阻塞,在Java中無法區(qū)分,仍然認為是可運行的) - BLOCKED : WAITING TIMED_WAITING 都是java API層面對
阻塞狀態(tài)的細分 - TERMINATED : 當線程代碼運行結(jié)束
六種狀態(tài)演示
public class StateTest {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
System.out.println("running");
},"t1");
Thread t2 = new Thread(()->{
while(true){
}
},"t2");
t2.start();
Thread t3 = new Thread(()->{
System.out.println("running");
},"t3");
t3.start();
Thread t4 = new Thread(()->{
synchronized (StateTest.class){
try{
Thread.sleep(100000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
},"t4");
t4.start();
Thread t5 = new Thread(()->{
try{
t2.join();
}catch (InterruptedException e){
e.printStackTrace();
}
},"t5");
t5.start();
Thread t6 = new Thread(()->{
synchronized (StateTest.class){
try{
Thread.sleep(100000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
},"t6");
t6.start();
try{
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("t1 state is " + t1.getState());
System.out.println("t2 state is " + t2.getState());
System.out.println("t3 state is " + t3.getState());
System.out.println("t4 state is " + t4.getState());
System.out.println("t5 state is " + t5.getState());
System.out.println("t6 state is " + t6.getState());
}
}
執(zhí)行結(jié)果:
running
t1 state is NEW
t2 state is RUNNABLE
t3 state is TERMINATED
t4 state is TIMED_WAITING
t5 state is WAITING
t6 state is BLOCKED