给我十分钟带你过完java多线程所有基础知识
目錄:
1.并發(fā)并行與線程進(jìn)程
2.認(rèn)識CPU和主線程
3.多線程的三種創(chuàng)建方式
4.三種創(chuàng)建多線程方式的進(jìn)一步探究和對比
5.匿名內(nèi)部類的多線程創(chuàng)建
6.多線程內(nèi)存的分析
7.深度了解線程run()和start()方法的作用
8.獲取和設(shè)置線程的名字
9.多線程多個(gè)窗口賣票的安全問題及三種解決方法
10.線程的五種狀態(tài)
11.消費(fèi)者生產(chǎn)者問題
12.線程常用方法總結(jié)
1.并發(fā)并行與線程進(jìn)程
(1) 并發(fā)并行
并發(fā):指兩個(gè)或者多個(gè)事件在同一個(gè)時(shí)間段發(fā)生(交替執(zhí)行)
并行:指兩個(gè)或者多個(gè)在同一時(shí)刻發(fā)生(同時(shí)執(zhí)行)
并發(fā)就是你有兩袋辣條,你想知道兩袋辣條哪一個(gè)袋好吃,所以你就先打開第一袋吃一口,然后又打開第二袋吃一口,你發(fā)現(xiàn)都好吃,所以你就換這吃,吃一口第一袋,再吃一口第二袋,再吃一口第一袋,再第二袋再…,直到把兩袋辣條吃完
并行就是你有兩袋辣條你分給你女朋友(醒醒你不可能有女朋友)一袋吃,讓后你倆同時(shí)吃,直到把辣條吃完
并發(fā)就是CPU在執(zhí)任務(wù)的時(shí)候同時(shí)在多個(gè)任務(wù)之間切換,并行就是同時(shí)擁有多個(gè)CPU同時(shí)執(zhí)行任務(wù)
(2)線程與進(jìn)程
進(jìn)程:指一個(gè)內(nèi)存中運(yùn)行的應(yīng)用程序,每個(gè)進(jìn)程都有一個(gè)獨(dú)立的內(nèi)存空間,一個(gè)應(yīng)用程序可以同時(shí)運(yùn)行多個(gè)進(jìn)程,進(jìn)程是程序的一次執(zhí)行過程。
線程:線程是進(jìn)程的一個(gè)執(zhí)行單位,一個(gè)進(jìn)程可以有多個(gè)線程。一個(gè)程序運(yùn)行后至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程可以有多個(gè)線程
2.認(rèn)識CPU和主線程
(1)CPU:中央處理器,對數(shù)據(jù)進(jìn)行計(jì)算指揮電腦中軟件和硬件干活
CPU分為AMD(單核心單線程CPU)和Inter(多核心多線程CPU)具體下圖有
對于上圖補(bǔ)充解釋:
應(yīng)用程序保存在硬盤中,打開應(yīng)用程序,在內(nèi)存中會(huì)打開一塊空間用于執(zhí)行應(yīng)用程序,進(jìn)入內(nèi)存的程序叫進(jìn)程
點(diǎn)擊應(yīng)用程序其中的功能(功能由代碼實(shí)現(xiàn)),然后就會(huì)開啟一條應(yīng)用程序到CPU的執(zhí)行路徑,CPU可以通過路徑執(zhí)行功能,這個(gè)路徑有個(gè)名字叫做線程
(2)執(zhí)行Main的方法的進(jìn)程叫做主線程
jvm在執(zhí)行main方法時(shí),main方法進(jìn)入棧內(nèi)存, jvm會(huì)讓操作系統(tǒng)開辟一條cpu到main方法的路徑叫主線程
3.創(chuàng)建多線程的三種方式
(1)繼承Thread類創(chuàng)建多線程
分為四步:
- 1.定義繼承Thread的子類
- 2.重寫該類的run()方法,將線程執(zhí)行的操作聲明在run()的方法里
- 3.創(chuàng)建Thread類子類的對象
- 調(diào)用start()方法啟動(dòng)線程
(2)實(shí)現(xiàn)Runable接口
分為五步:
- 1.定義一個(gè)實(shí)現(xiàn)Runable接口的類
- 2.重寫run()方法設(shè)置線程任務(wù)
- 3.創(chuàng)建實(shí)現(xiàn)類對象
- 4.將創(chuàng)建好的實(shí)現(xiàn)類對象傳到Thread類的構(gòu)造器中創(chuàng)建Thread對象
- 5.調(diào)用start()啟動(dòng)多線程
例子;
package untl1; public class MyThread implements Runnable {public void run(){for (int i = 0; i <100 ; i++) {System.out.println("我已經(jīng)數(shù)到"+i+"了");}}public static void main(String[] args) {MyThread p1=new MyThread();Thread p2=new Thread(p1);p2.start();for (int i = 0; i <100 ; i++) {System.out.println("hello world!");}} }(3)使用Callable和Future創(chuàng)建線程
分為五步:
- 1.創(chuàng)建一個(gè)Callable的實(shí)現(xiàn)類,實(shí)現(xiàn)call方法,并將此線程的任務(wù)放在call方法里
- 2.創(chuàng)建Callable的實(shí)現(xiàn)類對象
- 3.將此對象傳到FuterTask(是Futer的實(shí)現(xiàn)類,下一塊會(huì)講)構(gòu)造器里,創(chuàng)建FuterTask對象
- 4.將創(chuàng)建的FuterTask對象傳入Thread的構(gòu)造器中,創(chuàng)建Thread對象
- 5.調(diào)用FuterTask對象的get()方法獲得結(jié)束后的返回值,需要我們手動(dòng)拋出或者捕獲異常
4.三種創(chuàng)建多線程方式的進(jìn)一步探究和對比
(1)第一種創(chuàng)建多線程的方式,為啥要繼承Thread呢?是抽象類?
答案當(dāng)然是NO,你可以直接創(chuàng)建Thread的對象但是run方法里沒有線程任務(wù),所以我們需要繼承Thread和重寫run方法
(2)關(guān)于Future和FutureTask的繼承關(guān)系
(3) Callable和Future
Callable 接口類似于Runnable,從名字就可以看出來了,但是 Runnable 不會(huì)返回結(jié)果,并且無法拋出返回結(jié)果的異常,而 Callable 功能更強(qiáng)大一些,被線程執(zhí)行后,可以返回值,這個(gè)返回值可以被 Future 拿到,也就是說,Future 可以拿到異步執(zhí)行任務(wù)的返回值。
Future 接口表示異步任務(wù),是一個(gè)可能還沒有完成的異步任務(wù)的結(jié)果。所以說 Callable用于產(chǎn)生結(jié)果,Future 用于獲取結(jié)果。
(4)FutureTask
表示一個(gè)異步運(yùn)算的任務(wù)。FutureTask 里面可以傳入一個(gè) Callable 的具體實(shí)現(xiàn)類,可以對這個(gè)異步運(yùn)算的任務(wù)的結(jié)果進(jìn)行等待獲取、判斷是否已經(jīng)完成、取消任務(wù)等操作。只有當(dāng)運(yùn)算完成的時(shí)候結(jié)果才能取回,如果運(yùn)算尚未完成 get 方法將會(huì)阻塞。一個(gè) FutureTask 對象可以對調(diào)用了 Callable 和 Runnable 的對象進(jìn)行包裝,由于FutureTask 也是Runnable 接口的實(shí)現(xiàn)類,所以 FutureTask 也可以放入線程池中。
(5)關(guān)于繼承Thread和實(shí)現(xiàn)Runable的區(qū)別:
- 1.第一種方法直接繼承了Thread,因?yàn)閖ava是單繼承所以,繼承了Thread就不能繼承其他的類了,第二種沒有類的單繼承局限性,只是實(shí)現(xiàn)了接口而且java支持實(shí)現(xiàn)多個(gè)接口
- 2.Runable可以發(fā)生多態(tài),即傳入Thread構(gòu)造器中不同類型的對象就會(huì)有不同的線程任務(wù)
- 3.在run()方法內(nèi)獲取當(dāng)前線程,繼承Thread方法下可以使用this不用使用Thread.currentThread(),但是在Runable的實(shí)現(xiàn)類不能使用this必須使用Thread.currentThread()
(6)實(shí)現(xiàn)Runnable和實(shí)現(xiàn)Callable的區(qū)別
- 1.Runnable 接口 run()方法無返回值
- 2.Callable 接口 call()方法有返回值,是個(gè)泛型,和Future、FutureTask配合可以用來獲取異步執(zhí)行的結(jié)果
- 3.Runnable 接口 run()方法只能拋出運(yùn)行時(shí)異常,且無法捕獲處理;Callable 接口 call() 方法允許拋出和捕獲異常,可以獲取異常信息
5.匿名內(nèi)部類的多線程創(chuàng)建
第一種直接用Thread:
package acm; public class acm {public static void main(String[] args) {new Thread() {public void run(){for (int i = 0; i <10 ; i++) {System.out.println(i);}}unable}.start();}第二種new一個(gè)Runable接口放在Thread的構(gòu)造器里邊:
package acm; public class acm {public static void main(String[] args) {new Thread(new Runnable() {public void run() {for (int i = 0; i < 10; i++) {System.out.println(i);}}}).start();} }6.多線程內(nèi)存的分析
package untl1; public class MyThread {public static void main(String[] args) {for(int i=11;i<20;i++){System.out.println(i);}Thread p=new person();p.run();p.start();} } class person extends Thread{public void run() {for (int i = 0; i <10 ; i++) {System.out.println(i);}} }我們隨便拿上圖多線程的例子來說:
JVM在執(zhí)行main方法時(shí)找操作系統(tǒng)開辟一條CPU通往main方法的路徑,這條路徑叫做main線程,同理還會(huì)創(chuàng)建另一條路徑通往CPU,這條路徑就是我們創(chuàng)建的新的線程,兩個(gè)線程搶奪CPU的使用權(quán),誰搶到執(zhí)行誰的代碼,多個(gè)線程之間互不影響,因?yàn)樗鶎贄?臻g不同
兩個(gè)線程并發(fā)進(jìn)行,搶奪cpu使用權(quán)(使用時(shí)間,使用時(shí)間結(jié)束后,又開始新一輪的搶奪),java屬于搶占式調(diào)度,那個(gè)線程的優(yōu)先級高那個(gè)就先執(zhí)行,如果是同一優(yōu)先級那么隨機(jī)選擇一個(gè)執(zhí)行。
7.線程run()和start()方法
| run() | 每個(gè)線程都是通過某個(gè)特定Thread對象所對應(yīng)的方法run()來完成其操作的,run()方法稱為線程體。run() 方法用于執(zhí)行線程的運(yùn)行時(shí)代碼。可以重復(fù)調(diào)用 |
| start() | 通過調(diào)用Thread類的start()方法來啟動(dòng)一個(gè)線程 ,而且 start() 只能調(diào)用一次。 |
那么還有一個(gè)問題啟動(dòng)多線程為啥不直接調(diào)用run方法而是調(diào)用start再間接調(diào)用run方法?
首先我們要了解兩點(diǎn):
1.start()方法來啟動(dòng)一個(gè)線程,真正實(shí)現(xiàn)了多線程運(yùn)行。調(diào)用start()方法無需等待run方法體代碼執(zhí)行完畢,可以直接繼續(xù)執(zhí)行其他的代碼; 此時(shí)線程是處于就緒狀態(tài),并沒有運(yùn)行。 然后通過此Thread類調(diào)用方法run()來完成其運(yùn)行狀態(tài), run()方法運(yùn)行結(jié)束, 此線程終止。然后CPU再調(diào)度其它線程。
2.run()方法是在本線程里的,只是線程里的一個(gè)函數(shù),而不是多線程的。 如果直接調(diào)用run(),其實(shí)就相當(dāng)于是調(diào)用了一個(gè)普通函數(shù)而已,直接待用run()方法必須等待run()方法執(zhí)行完畢才能執(zhí)行下面的代碼,所以執(zhí)行路徑還是只有一條,根本就沒有線程的特征,所以在多線程執(zhí)行時(shí)要使用start()方法而不是run()方法。
了解之后就可以總結(jié)為:
new 一個(gè) Thread,線程進(jìn)入了新建狀態(tài)。調(diào)用 start() 方法,會(huì)啟動(dòng)一個(gè)線程并使線程進(jìn)入了就緒狀態(tài),當(dāng)分配到時(shí)間片后就可以開始運(yùn)行了。 start() 會(huì)執(zhí)行線程的相應(yīng)準(zhǔn)備工作,然后自動(dòng)執(zhí)行 run() 方法的內(nèi)容,這是真正的多線程工作。而直接執(zhí)行 run() 方法,會(huì)把 run 方法當(dāng)成一個(gè) main 線程下的普通方法去執(zhí)行,并不會(huì)在某個(gè)線程中執(zhí)行它,所以這并不是多線程工作。
8.獲取和設(shè)置線程的名字
(1)獲取線程的名字:
第一種Thread類中有g(shù)etname()方法獲得線程名字:
第二種可以先獲得當(dāng)前所在線程,再使用getname()方法:
package untl1; public class MyThread extends Thread {public void run(){String name=getName();System.out.println(name);}public static void main(String[] args) {MyThread p=new MyThread();p.start();Thread a=Thread.currentThread();System.out.println(a.getName());} }Thread.currentThread()是一個(gè)靜態(tài)方法可以獲得當(dāng)前線程
(2)設(shè)置線程的名字
第一種方式:
用對象名.setname("名字”)直接修改
第二種方式:
調(diào)用父親的構(gòu)造方法傳參幫子類線程取名字
9.多線程多個(gè)窗口賣票的安全問題
首先我們有三個(gè)窗口賣100張票,分別是窗口1窗口2和窗口3
我們使用多線程的對象來模擬三個(gè)窗口來賣票
我們?yōu)榱耸谷齻€(gè)窗口的票是共享的所以我們把票聲明成static
然后進(jìn)行賣票
如下:
package untl1; public class MyThread extends Thread {private static int ticket=100;public void run() {while(true){if(ticket>0){System.out.println("第"+Thread.currentThread().getName()+"在賣第"+ticket+"票");ticket--;}else{System.out.println("第"+Thread.currentThread().getName()+"已經(jīng)無票可賣");break;}}}public static void main(String[] args) {MyThread oneThread=new MyThread();MyThread twoThread=new MyThread();MyThread threeThread=new MyThread();oneThread.setName("一號窗口");twoThread.setName("二號窗口");threeThread.setName("三號窗口");oneThread.start();twoThread.start();threeThread.start();} }sleep靜態(tài)方法是Thread里邊的靜態(tài)方法,以毫秒為單位使程序進(jìn)入睡眠狀態(tài),在run方法中使用的時(shí)候要進(jìn)行捕獲異常的操作
由于運(yùn)行結(jié)果太長就不展示了,我總結(jié)一下以上代碼會(huì)出現(xiàn)的問題
1.會(huì)出現(xiàn)賣重復(fù)的票,賣不存在的票,以及有的票號沒有賣出。
賣重復(fù)的票是因?yàn)楫?dāng)cpu在執(zhí)行到語句后喪失了cpu的使用權(quán)(此時(shí)還沒執(zhí)行if里邊的內(nèi)容),被另一個(gè)線程搶到了cpu的使用權(quán),同樣的情況,在沒執(zhí)行if里邊的內(nèi)容就被別的線程搶走使用權(quán)。然后第一次的哪個(gè)線程搶到使用權(quán)后,還沒來得及自減就被別的線程搶走了cpu使用權(quán),然后就出現(xiàn)重復(fù)。
不存在的票是當(dāng)三個(gè)都進(jìn)入了if循環(huán),但是此時(shí)票只剩下1張,打印結(jié)束后還沒來得及自減,就被別的線程搶走了cpu的使用權(quán),別的線程又打印0張票,最后一個(gè)打印-1張票。
有的票沒賣出,就比如99號票,當(dāng)重復(fù)賣出100張票的時(shí)候幾個(gè),如果兩個(gè)線程都執(zhí)行到ticket–,而且在執(zhí)行這兩個(gè)tacket–的時(shí)候沒有賣出過票,那么就會(huì)有票漏賣
2.必須使用static才能實(shí)現(xiàn)數(shù)據(jù)的共享
那么如何解決呢
第一個(gè)問題,我們稱為線程安全問題,我們可以通過java同步機(jī)制解決
第二個(gè)問題我們可以使用一個(gè)實(shí)現(xiàn)了Runable接口的類所實(shí)例化的對象傳給Thread的不同引用,那么數(shù)據(jù)一樣可以共享
我們先用代碼解決第二個(gè)問題:
package untl1; public class MyThread implements Runnable{private int ticket=100;public void run() {while(true){if(ticket>0){try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("第"+Thread.currentThread().getName()+"在賣第"+ticket+"張票");ticket--;}else{System.out.println("第"+Thread.currentThread().getName()+"已經(jīng)無票可賣");break;}}}public static void main(String[] args) {MyThread myThread=new MyThread();Thread oneThread=new Thread(myThread);Thread twoThread=new Thread(myThread);Thread threeThread=new Thread(myThread);oneThread.setName("一號窗口");twoThread.setName("二號窗口");threeThread.setName("三號窗口");oneThread.start();twoThread.start();threeThread.start();} }那么解決第一個(gè)問題有三種方式
1.同步代碼塊
2.同步方法
3.鎖機(jī)制
其實(shí)我們要解決的根本問題是當(dāng)一個(gè)線程在執(zhí)行共享數(shù)據(jù)的時(shí)候,另外的所有線程要等到它執(zhí)行完成后再執(zhí)行,那么就不會(huì)發(fā)生線程安全問題了,所有解決方法都是以此為原理展開的
1.同步代碼塊
synchronized(鎖對象){ 這里邊存放可能出現(xiàn)線程安全的代碼塊(訪問了共享數(shù)據(jù)的代碼) }代碼詳解:
package untl1; public class MyThread implements Runnable{private int ticket=100;Object o=new Object();public void run() {while (true){synchronized (o){if(ticket>0){try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("第"+Thread.currentThread().getName()+"在賣第"+ticket+"張票");ticket--;}else{System.out.println("第"+Thread.currentThread().getName()+"已經(jīng)無票可賣");break;}}}}public static void main(String[] args) {MyThread myThread=new MyThread();Thread oneThread=new Thread(myThread);Thread twoThread=new Thread(myThread);Thread threeThread=new Thread(myThread);twoThread.setName("二號窗口");oneThread.setName("一號窗口");threeThread.setName("三號窗口");oneThread.start();twoThread.start();threeThread.start();} }首先鎖對象可以是任意的對象,但是必須保證多線程使用一個(gè)鎖對象,這就是我把鎖對象定義為類元素成員的原因
鎖對象的作用:
把同步代碼鎖住,只讓一個(gè)線程在同步代碼塊中進(jìn)行。
同步技術(shù)原理:
使用了一個(gè)鎖對象,這個(gè)鎖叫做同步鎖,也叫對象鎖,也叫對象監(jiān)視器,三個(gè)線程一起搶奪cpu使用權(quán),誰搶到誰執(zhí)行run賣票,當(dāng)?shù)谝粋€(gè)線程遇到synchronized代碼塊會(huì)檢查是否有鎖對象,有的話獲取鎖對象進(jìn)入同步中執(zhí)行,(只有拿到鎖對象才能進(jìn)入同步代碼塊中執(zhí)行,否則,不能執(zhí)行)。當(dāng)?shù)谝粋€(gè)線程的代碼塊沒執(zhí)行完cpu就被第二個(gè)線程搶走,那么檢查鎖對象,結(jié)果發(fā)現(xiàn)鎖對象被第一個(gè)線程拿著,所以第二個(gè)線程處于阻塞狀態(tài),只有第一個(gè)線程執(zhí)行完代碼塊中的內(nèi)容才會(huì)歸還(沒執(zhí)行完不會(huì)釋放鎖),然后執(zhí)行第二個(gè)線程,這樣就會(huì)避免多線程的安全問題。
同步代碼塊的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):保證了一個(gè)線程執(zhí)行共享數(shù)據(jù)里邊的內(nèi)容,保證了線程安全。
缺點(diǎn):由于程序頻繁的判斷鎖獲取鎖,釋放鎖程序效率比較低。
2.同步方法(分為靜態(tài)方法和非靜態(tài)方法)
訪問修飾符 (static) synchronized 返回值類型 方法名(){ 這里邊存放可能出現(xiàn)線程安全的代碼塊(訪問了共享數(shù)據(jù)的代碼) }代碼詳解:
package untl1; public class MyThread implements Runnable{private int ticket=100;Object o=new Object();public synchronized void func(){if(ticket>0){try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("第"+Thread.currentThread().getName()+"在賣第"+ticket+"張票");ticket--;}}public void run(){while (true){if(ticket>0){func();}else{System.out.println("第"+Thread.currentThread().getName()+"已經(jīng)無票可賣");break;}}}public static void main(String[] args) {MyThread myThread=new MyThread();Thread oneThread=new Thread(myThread);Thread twoThread=new Thread(myThread);Thread threeThread=new Thread(myThread);twoThread.setName("二號窗口");oneThread.setName("一號窗口");threeThread.setName("三號窗口");oneThread.start();twoThread.start();threeThread.start();} }其實(shí)同步方法也是利用的鎖對象鎖住,鎖對象就是this,對于上述例子來說就是myThread這個(gè)對象
以上是非靜態(tài)方法,那么靜態(tài)方法就是方法帶上static,并且ticket也要帶上static,我們都知道靜態(tài)方法內(nèi)沒有this對象,所以靜態(tài)同步方法的鎖對象變成本類生成的class文件對象(在反射里講在這里不必深究了解即可)
3.鎖機(jī)制
實(shí)際上就是利用java中的Lock接口,里邊有兩個(gè)方法:
void lock();獲取鎖
void unlock();釋放鎖
在可能出現(xiàn)線程安全的代碼塊前獲取鎖,在可能出現(xiàn)線程安全地代碼塊后釋放鎖,但是前提也是同一個(gè)Lock接口實(shí)現(xiàn)類對象
在java中有一個(gè)Lock接口實(shí)現(xiàn)類ReentrantLock
代碼實(shí)現(xiàn):
package untl1; import java.util.concurrent.locks.ReentrantLock; public class MyThread implements Runnable{private int ticket=100;ReentrantLock lock=new ReentrantLock();public void run() {while (true){lock.lock();if(ticket>0){try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("第"+Thread.currentThread().getName()+"在賣第"+ticket+"張票");ticket--;}else{System.out.println("第"+Thread.currentThread().getName()+"已經(jīng)無票可賣");break;}lock.unlock();}}public static void main(String[] args) {MyThread myThread=new MyThread();Thread oneThread=new Thread(myThread);Thread twoThread=new Thread(myThread);Thread threeThread=new Thread(myThread);twoThread.setName("二號窗口");oneThread.setName("一號窗口");threeThread.setName("三號窗口");oneThread.start();twoThread.start();threeThread.start();} }10.線程的五種狀態(tài)
| New(新建) | 新創(chuàng)建了一個(gè)線程對象 |
| Runable(可運(yùn)行狀態(tài)也稱就緒狀態(tài)) | 線程對象創(chuàng)建后,當(dāng)調(diào)用線程對象的start()方法,該線程處于就緒狀態(tài),等待被線程調(diào)度選中,獲取cpu的使用權(quán)。 |
| Runing(運(yùn)行狀態(tài)) | 可運(yùn)行狀態(tài)(runnable)的線程獲得了cpu時(shí)間片(timeslice),執(zhí)行程序代碼。注:就緒狀態(tài)是進(jìn)入到運(yùn)行狀態(tài)的唯一入口,也就是說,線程要想進(jìn)入運(yùn)行狀態(tài)執(zhí)行,首先必須處于就緒狀態(tài)中; |
| Blocked(阻塞狀態(tài)) | 處于運(yùn)行狀態(tài)中的線程由于某種原因,暫時(shí)放棄對 CPU的使用權(quán),停止執(zhí)行,此時(shí)進(jìn)入阻塞狀態(tài),直到其進(jìn)入到就緒狀態(tài),才 有機(jī)會(huì)再次被 CPU 調(diào)用以進(jìn)入到運(yùn)行狀態(tài)。阻塞的情況分三種:(一). 等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行 wait()方法,JVM會(huì)把該線程放入等待隊(duì)列(waitting queue)中,使本線程進(jìn)入到等待阻塞狀態(tài);(二). 同步阻塞:線程在獲取 synchronized 同步鎖失敗(因?yàn)殒i被其它線程所占用),,則JVM會(huì)把該線程放入鎖池(lock pool)中,線程會(huì)進(jìn)入同步阻塞狀態(tài);(三). 其他阻塞: 通過調(diào)用線程的 sleep()或 join()或發(fā)出了 I/O 請求時(shí),線程會(huì)進(jìn)入到阻塞狀態(tài)。當(dāng) sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)、或者 I/O 處理完畢時(shí),線程重新轉(zhuǎn)入就緒狀態(tài)。 |
| Dead(死亡狀態(tài)) | 線程run()、main()方法執(zhí)行結(jié)束,或者因異常退出了run()方法,則該線程結(jié)束生命周期。死亡的線程不可再次復(fù)生。 |
11.消費(fèi)者生產(chǎn)者問題
生產(chǎn)者與消費(fèi)者問題其實(shí)就是等待喚醒機(jī)制,等待喚醒機(jī)制是一種協(xié)作機(jī)制,平常線程之間都是競爭關(guān)系,比如競爭鎖,但是并不是所有線程都是競爭關(guān)系,就像我們現(xiàn)在講的協(xié)作關(guān)系。等待喚醒的一種簡單實(shí)現(xiàn)是利用以下兩個(gè)方法
1.wait():生產(chǎn)者或消費(fèi)者線程停止自己的執(zhí)行,釋放鎖,使自己處于等待狀態(tài),讓其它線程執(zhí)行。
2.notify():向其他等待的線程發(fā)出通知,同時(shí)釋放鎖,使自己處于等待狀態(tài),讓其它線程執(zhí)行。
場景:顧客來買包子,賣包子的人沒包子了,又由于顧客一年沒吃飯了,所以賣包子的人做好一個(gè)提醒顧客吃一個(gè)(別問為啥賣包子的一次只能做一個(gè))
整體思路:
1.創(chuàng)建一個(gè)顧客線程:告知老板要的包子的數(shù)量和種類,調(diào)用wait()方法,放棄cpu的執(zhí)行,進(jìn)入等待狀態(tài)
2.創(chuàng)建一個(gè)老板線程,包子做好后調(diào)用notify()方法,喚醒顧客吃包子
3.顧客和老板必須用同步代碼塊包裹起來,保證等待和喚醒只有一個(gè)在執(zhí)行
代碼實(shí)現(xiàn):
package untl1; public class acm{public static void main(String[] args) {Object p=new Object();person p1=new person(p);Thread p2=new Thread(p1);shopower p3=new shopower(p);Thread p4=new Thread(p3);p2.start();p4.start();} } class person implements Runnable{Object p;person(Object p){this.p=p;}public void run(){while(true){synchronized (p){System.out.println("我要買包子老板");try {p.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("吃光了");}}} } class shopower implements Runnable{Object p;shopower(Object p){this.p=p;}public void run(){while(true){try {Thread.sleep(500);//在父類的run未拋異常子類也不能拋} catch (InterruptedException e) {e.printStackTrace();}synchronized (p){System.out.println("包子花五秒做好了,請吃把");p.notify();}}} }12.線程常用方法總結(jié)
1.如圖:
| setName (String) ; | 設(shè)置線程的名稱 |
| String getName () | 返回線程的名稱 |
| start() | 啟動(dòng)線程,并執(zhí)行對象的 run() 方法 |
| run() | 線程在被調(diào)度時(shí)執(zhí)行的 操作,子線程要執(zhí)行的代碼放入run()方法 |
| interrupt() | 中斷線程,由運(yùn)行狀態(tài)到死亡狀態(tài) |
| join(long millis) | 等待該線程終止的時(shí)間最長為 millis 毫秒。join()方法會(huì)使當(dāng)前線程等待調(diào)用 join() 方法的線程結(jié)束后才能繼續(xù)執(zhí)行。注意該方法也需要捕捉異常。 |
| sleep(long millis) | 靜態(tài)方法,睡眠指定時(shí)間,程序暫停運(yùn)行,睡眠期間會(huì)讓出CPU的執(zhí)行權(quán),去執(zhí)行其它線程,同時(shí)CPU也會(huì)監(jiān)視睡眠的時(shí)間,一旦睡眠時(shí)間到就會(huì)立刻執(zhí)行(因?yàn)樗哌^程中仍然保留著鎖,有鎖只要睡眠時(shí)間到就能立刻執(zhí)行)。 |
| yield() | 是一種靜態(tài)的方法,暫停當(dāng)前正在執(zhí)行的線程對象,并執(zhí)行其他線程。 |
| stop() | 強(qiáng)制線程生命周期結(jié)束,已經(jīng)廢止 |
| wait() | 一旦執(zhí)行此方法,當(dāng)前線程就進(jìn)入阻塞狀態(tài),并釋放鎖。而當(dāng)前線程 排隊(duì) 等候其他線程調(diào)用notify() 或 notifyAll() 方法喚醒,喚醒后等待重新獲得對監(jiān)視器的所有 權(quán)后才能繼續(xù)執(zhí)行. |
| notify() | 一旦執(zhí)行此方法,就會(huì)喚醒被wait的一個(gè)線程。如果有多個(gè)線程被wait,就喚醒優(yōu)先級高的那個(gè),如果優(yōu)先級相同就喚醒等待時(shí)間最長的哪一個(gè)。 |
| notifyAll() | 一旦執(zhí)行此方法,就會(huì)喚醒所有被wait的線程。 |
以上方法除了wait()、notify()和notifyAll()定義在Object這個(gè)類里邊其他都定義在Thread里
2.關(guān)于notify()和notifyAll()的區(qū)別:
這里是引用如果線程調(diào)用了對象的 wait()方法,那么線程便會(huì)處于該對象的等待池中,等待池中的線程不會(huì)去競爭該對象的鎖。notifyAll() 會(huì)喚醒所有的線程,notify() 只會(huì)喚醒一個(gè)線程。notifyAll() 調(diào)用后,會(huì)將全部線程由等待池移到鎖池,然后參與鎖的競爭,競爭成功則繼續(xù)執(zhí)行,如果不成功則留在鎖池等待鎖被釋放后再次參與競爭。而 notify()只會(huì)喚醒一個(gè)線程,具體喚醒哪一個(gè)線程由虛擬機(jī)控制。
3.sleep()方法和 yield()方法有什么區(qū)別?
(1) sleep()方法給其他線程運(yùn)行機(jī)會(huì)不考慮線程的優(yōu)先級,因此會(huì)給低優(yōu)先級的線程以運(yùn)行的機(jī)會(huì);yield()方法只會(huì)給相同優(yōu)先級或更高優(yōu)先級的線程以運(yùn)行的機(jī)會(huì);
(2) 線程執(zhí)行 sleep()方法后轉(zhuǎn)入阻塞(blocked)狀態(tài),而執(zhí)行 yield()方法后轉(zhuǎn)入就緒(ready)狀態(tài);
(3)sleep()方法聲明拋出 InterruptedException,而 yield()方法沒有聲明任何異常;
(4)sleep()方法比 yield()方法(跟操作系統(tǒng) CPU 調(diào)度相關(guān))具有更好的可移植性,通常不建議使用yield()方法來控制并發(fā)線程的執(zhí)行。
4.sleep() 和 wait() 兩者都可以暫停線程那有什么區(qū)別?
1.類的不同:sleep() 是 Thread線程類的靜態(tài)方法,wait() 是 Object類的方法
2.是否釋放鎖:sleep() 不釋放鎖;wait() 釋放鎖。
3.用途不同:Wait 通常被用于線程間交互/通信,sleep 通常被用于暫停執(zhí)行。
4.用法不同:wait() 方法被調(diào)用后,線程不會(huì)自動(dòng)蘇醒,需要?jiǎng)e的線程調(diào)用同一個(gè)對象上的 notify() 或者 notifyAll() 方法。sleep() 方法執(zhí)行完成后,線程會(huì)自動(dòng)蘇醒。或者可以使用wait(long timeout)超時(shí)后線程會(huì)自動(dòng)蘇醒。
總結(jié)
以上是生活随笔為你收集整理的给我十分钟带你过完java多线程所有基础知识的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java网络编程从0——》入门
- 下一篇: Comparable接口和Compara