滑動 手機(jī)網(wǎng)站 代碼優(yōu)化人員配置
- 認(rèn)識多線程
- 掌握多線程程序的編寫
- 掌握多線程的狀態(tài)
一. 認(rèn)識線程(Thread)
1概念
1) 線程是什么
??????????個線程就是?個 "執(zhí)?流". 每個線程之間都可以按照順序執(zhí)???的代碼. 多個線程之間 "同時" 執(zhí)?著多份代碼.
????????還是回到我們之前的銀?的例?中。之前我們主要描述的是個?業(yè)務(wù),即?個?完全處理??的業(yè)務(wù)。我們進(jìn)?步設(shè)想如下場景:
?????????家公司要去銀?辦理業(yè)務(wù),既要進(jìn)?財務(wù)轉(zhuǎn)賬,?要進(jìn)?福利發(fā)放,還得進(jìn)?繳社保。????????如果只有張三?個會計就會忙不過來,耗費的時間特別?。為了讓業(yè)務(wù)更快的辦理好,張三?找來兩位同事李四、王五?起來幫助他,三個?分別負(fù)責(zé)?個事情,分別申請?個號碼進(jìn)?排隊,?此就有了三個執(zhí)?流共同完成任務(wù),但本質(zhì)上他們都是為了辦理?家公司的業(yè)務(wù)。????????此時,我們就把這種情況稱為多線程,將?個?任務(wù)分解成不同?任務(wù),交給不同執(zhí)?流就分別排隊 執(zhí)?。其中李四、王五都是張三叫來的,所以張三?般被稱為主線程(Main Thread)。
2) 為啥要有線程
?先, "并發(fā)編程" 成為 "剛需".
- 單核 CPU 的發(fā)展遇到了瓶頸. 要想提?算?, 就需要多核 CPU. ?并發(fā)編程能更充分利?多核 CPU資源.
- 有些任務(wù)場景需要 "等待 IO", 為了讓等待 IO 的時間能夠去做?些其他的?作, 也需要?到并發(fā)編程.
其次, 雖然多進(jìn)程也能實現(xiàn) 并發(fā)編程, 但是線程?進(jìn)程更輕量.(線程就是輕量級進(jìn)程)
- 創(chuàng)建線程?創(chuàng)建進(jìn)程更快.
- 銷毀線程?銷毀進(jìn)程更快.
- 調(diào)度線程?調(diào)度進(jìn)程更快.
最后, 線程雖然?進(jìn)程輕量, 但是?們還不滿?, 于是?有了 "線程池"(ThreadPool) 和 "協(xié)程"(Coroutine)
3) 進(jìn)程和線程的區(qū)別
- 進(jìn)程是包含線程的. 每個進(jìn)程?少有?個線程存在,即主線程。
- 進(jìn)程和進(jìn)程之間不共享內(nèi)存空間. 同?個進(jìn)程的線程之間共享同?個內(nèi)存空間.
?如之前的多進(jìn)程例?中,每個客?來銀?辦理各?的業(yè)務(wù),但他們之間的票據(jù)肯定是不想讓別?知道的,否則錢不就被其他?取?了么。?上?我們的公司業(yè)務(wù)中,張三、李四、王五雖然是不同的執(zhí)?流,但因為辦理的都是?家公司的業(yè)務(wù),所以票據(jù)是共享著的。這個就是多線程和多進(jìn)程的最?區(qū)別。
- 進(jìn)程是系統(tǒng)分配資源的最?單位,線程是系統(tǒng)調(diào)度的最?單位。
- ?個進(jìn)程掛了?般不會影響到其他進(jìn)程. 但是?個線程掛了, 可能把同進(jìn)程內(nèi)的其他線程?起帶?(整個進(jìn)程崩潰).
4) Java 的線程 和 操作系統(tǒng)線程 的關(guān)系
api:application programming interface(應(yīng)用程序編程接口)
- 操作系統(tǒng)提供的原生api是c寫的
- 不同操作系統(tǒng)的線程api不相同
2 第?個多線程程序
- 每個線程都是?個獨?的執(zhí)?流
- 多個線程之間是 "并發(fā)" 執(zhí)?的.





3 創(chuàng)建線程
?法1 繼承 Thread 類
class MyThread extends Thread{@Override//run相當(dāng)于線程的入口函數(shù)public void run() {System.out.println("hello world");}
}
(2)創(chuàng)建 MyThread 類的實例
Thread t=new MyThread();
(3)調(diào)? start ?法啟動線程
//真正在系統(tǒng)中創(chuàng)建出一個線程t.start();
(4)休眠
try {Thread.sleep(1000);//sleep是靜態(tài)方法,表示休眠,休息一會再執(zhí)行,用ms為單位} catch (InterruptedException e) {throw new RuntimeException(e);}
(5)run
????????在執(zhí)行線程時,不需要顯示運行run方法,JVM自動調(diào)用
Tip:
在自己寫的MyThread類里面不允許用throws,只能try-catch,因為其父類Thread里面沒有實現(xiàn)該功能,但main函數(shù)中可以
實際開發(fā)中,異常的處理方式
1.記錄異常信息作為日志.后續(xù)程序員根據(jù)日志調(diào)查問題
????????程序仍然正常往后執(zhí)行邏輯,不會因為這個異常就終止(對于服務(wù)器非常關(guān)鍵的)2.進(jìn)行重試,有的異常是概率性的(網(wǎng)絡(luò)通訊)
3.特別嚴(yán)重的問題,必須立即馬上處理的問題
????????通過短信/郵件/微信/電話 通知程序員 (報警機(jī)制)
服務(wù)器和客戶端指的是兩個程序
服務(wù)器(server):被動接受請求,返回響應(yīng)的一方
客戶端(client):主動發(fā)起請求的一方
- 客戶端給服務(wù)器發(fā)送的數(shù)據(jù),叫做“request"請求
- 服務(wù)器給客戶端返回的叫做“response”響應(yīng)
- 通常一個服務(wù)器可以給多個客戶端提供服務(wù)
- 服務(wù)器基本7*24待命
根據(jù)輸出可以知道:多個線程的調(diào)度是隨機(jī)的(“搶占式執(zhí)行”)
Q:可以控制輸出順序嗎?
A:輸出順序是操作系統(tǒng)內(nèi)核的調(diào)度器控制的,沒法在應(yīng)用應(yīng)用程序中編寫代碼控制 (調(diào)度器沒有提供 api 的)
唯一能做的就是給線程設(shè)置優(yōu)先級(但是優(yōu)先級,對于操作系統(tǒng)來說,也是僅供參考,不會嚴(yán)格的定量的遵守)
如果直接使用run方法,而沒有start,那么MyTread實質(zhì)上沒有創(chuàng)建出進(jìn)程,只有main進(jìn)程,遇到run中的死循環(huán)之后無法退出。
package Thread;
class MyThread extends Thread{@Override//run相當(dāng)于線程的入口函數(shù)public void run() {while(true){System.out.println("hello run");try {Thread.sleep(1000);//sleep是靜態(tài)方法,表示休眠,休息一會再執(zhí)行,用ms為單位} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
public class demo1 {public static void main(String[] args) throws InterruptedException {Thread t=new MyThread();//真正在系統(tǒng)中創(chuàng)建出一個線程//t.start();t.run();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}
?法2 實現(xiàn) Runnable 接?
class MyRunnable implements Runnable{@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
2. 創(chuàng)建 Thread 類實例, 調(diào)? Thread 的構(gòu)造?法時將 Runnable 對象作為 target 參數(shù).
Runnable runnable=new MyRunnable();
Thread t=new Thread(runnable);
3. 調(diào)? start ?法
t.start();
總代碼:
package Thread;
class MyRunnable implements Runnable{@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}public class demo2 {public static void main(String[] args) throws InterruptedException {Runnable runnable=new MyRunnable();Thread t=new Thread(runnable);t.start();while (true){System.out.println("hello main");Thread.sleep(1000);}}
}
Runnable最終還是要通過 Thread,真正創(chuàng)建線程
線程里要干啥, 通過 Runnable 來表示(而不是通過直接重寫 Thread的run 來表示了)
Q:如何判斷是用哪種?
A:根據(jù)線程要執(zhí)行的任務(wù)的定義,是放到 Thread 里面,還是放到外面(Runnable 中)
Q:使用Runnable有什么好處嗎?
A:解耦合。要執(zhí)行的任務(wù)本身,和 線程這個概念,能夠解耦合,從而后續(xù)如果變更代碼.(比如不通過線程執(zhí)行這個任務(wù),通過其他方式.….)
采用 Runnable 這樣的方案,代碼的修改就會更簡單.
對?上?兩種?法:
- 繼承 Thread 類, 直接使? this 就表?當(dāng)前線程對象的引?.
- 實現(xiàn) Runnable 接?, this 表?的是 MyRunnable 的引?. 需要使?Thread.currentThread()
?法3 實現(xiàn)Tread的匿名內(nèi)部類
package Thread;public class demo3 {public static void main(String[] args) throws InterruptedException {Thread thread=new Thread(){while(true){System.out.println("hello run");try {Thread.sleep(1000);//sleep是靜態(tài)方法,表示休眠,休息一會再執(zhí)行,用ms為單位} catch (InterruptedException e) {throw new RuntimeException(e);}}};thread.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}
?法4 匿名內(nèi)部類創(chuàng)建 Runnable ?類對象
package Thread;public class demo4 {public static void main(String[] args) throws InterruptedException {Runnable runnable = new Runnable() {@Overridepublic void run() {while (true) {System.out.println("hello run");try {Thread.sleep(1000);//sleep是靜態(tài)方法,表示休眠,休息一會再執(zhí)行,用ms為單位} catch (InterruptedException e) {throw new RuntimeException(e);}}}};Thread thread=new Thread(runnable);thread.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}
?法5??lambda 表達(dá)式創(chuàng)建 Runnable ?類對象
package Thread;public class demo5 {public static void main(String[] args) throws InterruptedException {Thread thread=new Thread(()->{while (true) {System.out.println("hello run");try {Thread.sleep(1000);//sleep是靜態(tài)方法,表示休眠,休息一會再執(zhí)行,用ms為單位} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}
4 多線程的優(yōu)勢-增加運?速度
- 使? System.nanoTime() 可以記錄當(dāng)前系統(tǒng)的 納秒 級時間戳.
- serial 串?的完成?系列運算.
- concurrency 使?兩個線程并?的完成同樣的運算.
二. Thread 類及常??法
????????Thread 類是 JVM ?來管理線程的?個類,換句話說,每個線程都有?個唯?的 Thread 對象與之關(guān)聯(lián)。
?????????我們上?的例?來看,每個執(zhí)?流,也需要有?個對象來描述,類似下圖所?,? Thread 類的對象就是?來描述?個線程執(zhí)?流的,JVM 會將這些 Thread 對象組織起來,?于線程調(diào)度,線程管理。
1 Thread 的常?構(gòu)造?法
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("這是我的名字");
Thread t4 = new Thread(new MyRunnable(), "這是我的名字");
main方法結(jié)束了,主線程就結(jié)束了
以前認(rèn)知里main方法結(jié)束,程序就執(zhí)行完畢是針對單線程程序的
2 Thread 的?個常?屬性
- ID 是線程的唯?標(biāo)識,不同線程不會重復(fù)
- 名稱是各種調(diào)試?具?到
- 狀態(tài)表?線程當(dāng)前所處的?個情況,下?我們會進(jìn)?步說明
- 優(yōu)先級?的線程理論上來說更容易被調(diào)度到
- 關(guān)于后臺線程,需要記住?點:JVM會在?個進(jìn)程的所有?后臺線程結(jié)束后,才會結(jié)束運?。
- 是否存活,即簡單的理解,為 run ?法是否運?結(jié)束了
- 線程的中斷問題,下?我們進(jìn)?步說明
public class ThreadDemo {public static void main(String[] args) {Thread thread = new Thread(() -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName() + ": 我還在");Thread.sleep(1 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我即將死去");});System.out.println(Thread.currentThread().getName()+ ": ID: " + thread.getId());System.out.println(Thread.currentThread().getName()+ ": 名稱: " + thread.getName());System.out.println(Thread.currentThread().getName()+ ": 狀態(tài): " + thread.getState());System.out.println(Thread.currentThread().getName()+ ": 優(yōu)先級: " + thread.getPriority());System.out.println(Thread.currentThread().getName()+ ": 后臺線程: " + thread.isDaemon());System.out.println(Thread.currentThread().getName()+ ": 活著: " + thread.isAlive());System.out.println(Thread.currentThread().getName()+ ": 被中斷: " + thread.isInterrupted());thread.start();while (thread.isAlive()) {}System.out.println(Thread.currentThread().getName()+ ": 狀態(tài): " + thread.getState());}
}
3 啟動?個線程 - start()
之前我們已經(jīng)看到了如何通過覆寫 run ?法創(chuàng)建?個線程對象,但線程對象被創(chuàng)建出來并不意味著線程就開始運?了。
- 覆寫 run ?法是提供給線程要做的事情的指令清單
- 線程對象可以認(rèn)為是把 李四、王五叫過來了
- ?調(diào)? start() ?法,就是喊?聲:”?動起來!“,線程才真正獨?去執(zhí)?了。
調(diào)? start ?法, 才真的在操作系統(tǒng)的底層創(chuàng)建出?個線程
4 中斷?個線程
????????李四?旦進(jìn)到?作狀態(tài),他就會按照?動指南上的步驟去進(jìn)??作,不完成是不會結(jié)束的。但有時我們需要增加?些機(jī)制,例如?板突然來電話了,說轉(zhuǎn)賬的對?是個騙?,需要趕緊停?轉(zhuǎn)賬,那張三該如何通知李四停?呢?這就涉及到我們的停?線程的?式了。
?前常?的有以下兩種?式:
- 通過共享的標(biāo)記來進(jìn)?溝通
- 調(diào)? interrupt() ?法來通知
?例1: 使??定義的變量來作為標(biāo)志位.
//需要給標(biāo)志位上加 volatile 關(guān)鍵字(這個關(guān)鍵字的功能后?介紹).
public class ThreadDemo {private static class MyRunnable implements Runnable {public volatile boolean isQuit = false;@Overridepublic void run() {while (!isQuit) {System.out.println(Thread.currentThread().getName()+ ": 別管我,我忙著轉(zhuǎn)賬呢!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+ ": 啊!險些誤了?事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");System.out.println(Thread.currentThread().getName()+ ": 讓李四開始轉(zhuǎn)賬。");thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName()+ ": ?板來電話了,得趕緊通知李四對?是個騙?!");target.isQuit = true;}
}
?例-2: 使? Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替?定義標(biāo)志位.
Thread 內(nèi)部包含了?個 boolean 類型的變量作為線程是否被中斷的標(biāo)記.
? 使? thread 對象的 interrupted() ?法通知線程結(jié)束.
public class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic void run() {// 兩種?法均可以while (!Thread.interrupted()) {
//while (!Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName() + ": 別管我,我忙著轉(zhuǎn)賬呢!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println(Thread.currentThread().getName() + ": 有內(nèi)?,終?交易!");// 注意此處的 breakbreak;}}System.out.println(Thread.currentThread().getName() + ": 啊!險些誤了?事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");System.out.println(Thread.currentThread().getName() + ": 讓李四開始轉(zhuǎn)賬。");thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName() + ": ?板來電話了,得趕緊通知李四對?是個騙?!");thread.interrupt();}
}
thread 收到通知的?式有兩種:
1. 如果線程因為調(diào)? wait/join/sleep 等?法?阻塞掛起,則以 InterruptedException 異常的形式通 知,清除中斷標(biāo)志
????????? 當(dāng)出現(xiàn) InterruptedException 的時候, 要不要結(jié)束線程取決于 catch 中代碼的寫法. 可以選擇忽略這個異常, 也可以跳出循環(huán)結(jié)束線程.
??????????? Thread.currentThread().isInterrupted() 判斷指定線程的中斷標(biāo)志被設(shè)置,不清除中斷標(biāo)志
2. 否則,只是內(nèi)部的?個中斷標(biāo)志被設(shè)置,thread 可以通過這種?式通知收到的更及時,即使線程正在 sleep 也可以?上收到。
??????
5 等待?個線程 - join()
有時,我們需要等待?個線程完成它的?作后,才能進(jìn)???的下?步?作。例如,張三只有等李四轉(zhuǎn)賬成功,才決定是否存錢,這時我們需要?個?法明確等待線程的結(jié)束。
package Thread;public class ThreadDemo1 {public static void main(String[] args) throws InterruptedException {Runnable target =() -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName()+ ": 我還在?作!");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我結(jié)束了!");};Thread thread1 = new Thread(target, "李四");Thread thread2 = new Thread(target, "王五");System.out.println("先讓李四開始?作");thread1.start();thread1.join();System.out.println("李四?作結(jié)束了,讓王五開始?作");thread2.start();thread2.join();System.out.println("王五?作結(jié)束了");}
}
?家可以試試如果把兩個 join 注釋掉,現(xiàn)象會是怎么樣的呢?
6 獲取當(dāng)前線程引?
public class ThreadDemo {public static void main(String[] args) {Thread thread = Thread.currentThread();System.out.println(thread.getName());}
}
7 休眠當(dāng)前線程
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(3 * 1000);System.out.println(System.currentTimeMillis());}
}
三、?線程的狀態(tài)
1 觀察線程的所有狀態(tài)
Thread.State
public class ThreadState {public static void main(String[] args) {for (Thread.State state : Thread.State.values()) {System.out.println(state);}}
}
- NEW: 安排了?作, 還未開始?動
- RUNNABLE: 可?作的. ?可以分成正在?作中和即將開始?作.
- BLOCKED: 這?個都表?排隊等著其他事情
- WAITING: 這?個都表?排隊等著其他事情
- TIMED_WAITING: 這?個都表?排隊等著其他事情
- TERMINATED: ?作完成了.
2. 線程狀態(tài)和狀態(tài)轉(zhuǎn)移的意義
?家不要被這個狀態(tài)轉(zhuǎn)移圖嚇到,我們重點是要理解狀態(tài)的意義以及各個狀態(tài)的具體意思。
還是我們之前的例?:
剛把李四、王五找來,還是給他們在安排任務(wù),沒讓他們?動起來,就是 NEW 狀態(tài);
當(dāng)李四、王五開始去窗?排隊,等待服務(wù),就進(jìn)?到 RUNNABLE 狀態(tài)。該狀態(tài)并不表?已經(jīng)被銀??作?員開始接待,排在隊伍中也是屬于該狀態(tài),即可被服務(wù)的狀態(tài),是否開始服務(wù),則看調(diào)度器的調(diào)度;
當(dāng)李四、王五因為?些事情需要去忙,例如需要填寫信息、回家取證件、發(fā)呆?會等等時,進(jìn)?BLOCKED 、 WATING 、TIMED_WAITING 狀態(tài),?于這些狀態(tài)的細(xì)分,我們以后再詳解;
如果李四、王五已經(jīng)忙完,為 TERMINATED 狀態(tài)。
所以,之前我們學(xué)過的 isAlive() ?法,可以認(rèn)為是處于不是 NEW 和 TERMINATED 的狀態(tài)都是活著的。
3 觀察線程的狀態(tài)和轉(zhuǎn)移
package Thread;public class ThreadStateTransfer {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 1000_0000; i++) {}}, "李四");System.out.println(t.getName() + ": " + t.getState());;t.start();while (t.isAlive()) {System.out.println(t.getName() + ": " + t.getState());;}System.out.println(t.getName() + ": " + t.getState());;}
}
觀察 2: 關(guān)注 WAITING 、 BLOCKED 、 TIMED_WAITING 狀態(tài)的轉(zhuǎn)換
public static void main(String[] args) {final Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}}, "t1");t1.start();Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {System.out.println("hehe");}}}, "t2");t2.start();}
}
使? jconsole 可以看到 t1 的狀態(tài)是 TIMED_WAITING , t2 的狀態(tài)是 BLOCKED
public static void main(String[] args) {final Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {// [修改這?就可以了!!!!!]// Thread.sleep(1000);object.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}, "t1");
...}
使? jconsole 可以看到 t1 的狀態(tài)是 WAITING
結(jié)論:
? BLOCKED 表?等待獲取鎖, WAITING 和 TIMED_WAITING 表?等待其他線程發(fā)來通知.
? TIMED_WAITING 線程在等待喚醒,但設(shè)置了時限; WAITING 線程在?限等待喚醒