<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試:再見(jiàn)多線程!

          共 9010字,需瀏覽 19分鐘

           ·

          2022-04-28 00:19

          ??Java大聯(lián)盟

          ? 致力于最高效的Java學(xué)習(xí)

          關(guān)注




          B 站搜索:楠哥教你學(xué)Java

          獲取更多優(yōu)質(zhì)視頻教程



          應(yīng)用場(chǎng)景

          1、異步處理,例如:發(fā)郵件、發(fā)微博、寫日志等;

          2、分布式計(jì)算

          3、定期執(zhí)行一些特殊任務(wù):如定期更新配置文件,任務(wù)調(diào)度(如quartz),一些監(jiān)控用于定期信息采集等

          4、TOMCAT處理多用戶請(qǐng)求。

          5、針對(duì)特別耗時(shí)的操作。多線程同步執(zhí)行可以提高速度。例如:定時(shí)向大量(100w以上)的用戶發(fā)送郵件。


          多線程編程面臨的挑戰(zhàn)及解決思路

          問(wèn)題一:上下文切換

          并發(fā)不一定快于串行,因?yàn)闀?huì)有切換上下文的開(kāi)銷。【切換上下文:?jiǎn)魏瞬l(fā)時(shí),cpu會(huì)使用時(shí)間片輪轉(zhuǎn)實(shí)現(xiàn)并發(fā),每一次輪轉(zhuǎn),會(huì)保留當(dāng)前執(zhí)行的狀態(tài)】。

          解決上下文切換開(kāi)銷的辦法:

          無(wú)鎖并發(fā)編程:多線程競(jìng)爭(zhēng)鎖時(shí),會(huì)引起上下文切換,所以多線程處理數(shù)據(jù)時(shí),可以用一些辦法來(lái)避免使用鎖,如將數(shù)據(jù)的ID按照Hash算法取模分段,不同的線程處理不同段的數(shù)據(jù)。

          • CAS算法:Java的Atomic包使用CAS算法來(lái)更新數(shù)據(jù),而不需要加鎖。

          • 使用最少線程:避免創(chuàng)建不需要的線程,比如任務(wù)很少,但是創(chuàng)建了很多線程來(lái)處理,這樣會(huì)造成大量線程都處于等待狀態(tài)。

          • 協(xié)程:在單線程里實(shí)現(xiàn)多任務(wù)的調(diào)度,并在單線程里維持多個(gè)任務(wù)間的切換。


          問(wèn)題二:死鎖

          死鎖是一個(gè)比較常見(jiàn)也比較難解決的問(wèn)題,當(dāng)多個(gè)線程等待同一個(gè)不會(huì)釋放的資源時(shí),就會(huì)發(fā)生死鎖。避免死鎖可以參考下面的思路。

          避免死鎖的方法:

          1. 避免一個(gè)線程同時(shí)獲取多個(gè)鎖。

          2. 避免一個(gè)線程在鎖內(nèi)同時(shí)占用多個(gè)資源,盡量保證每個(gè)鎖只占用一個(gè)資源。

          3. 嘗試使用定時(shí)鎖,使用lock.tryLock(timeout)來(lái)替代使用內(nèi)部鎖機(jī)制。

          4. 對(duì)于數(shù)據(jù)庫(kù)鎖,加鎖和解鎖必須在一個(gè)數(shù)據(jù)庫(kù)連接里,否則會(huì)出現(xiàn)解鎖失敗的情況。

          問(wèn)題三:資源限制

          資源限制是指在進(jìn)行并發(fā)編程時(shí),程序的執(zhí)行速度受限于計(jì)算機(jī)硬件資源或軟件資源。例如,服務(wù)器的帶寬只有2Mb/s,某個(gè)資源的下載速度是1M每秒,系統(tǒng)啟動(dòng)10個(gè)線程下載資源,下載速度不會(huì)變成10Mb/s

          解決資源限制思路:

          1. 對(duì)于硬件資源限制,可以考慮使用集群并行執(zhí)行程序。

          2. 對(duì)于軟件資源限制,可以考慮使用資源池將資源復(fù)用。比如使用連接池將數(shù)據(jù)庫(kù)和Socket連接復(fù)用,或者在調(diào)用對(duì)方webservice接口獲取數(shù)據(jù)時(shí),只建立一個(gè)連接。


          基礎(chǔ)示例

          實(shí)現(xiàn)多線程基本的實(shí)現(xiàn)方式就是如下兩種:

          繼承Thread類;

          實(shí)現(xiàn)Runnable接口;

          實(shí)際使用時(shí),會(huì)用到線程池,還會(huì)用spring管理線程池,下面使用多線程完成幾個(gè)小例子。

          示例一:多線程使用reentrantLock實(shí)現(xiàn)交替打印奇數(shù)偶數(shù),代碼見(jiàn)壓縮包:

          示例二:4個(gè)線程,兩個(gè)存錢,兩個(gè)取錢

          示例三:spring管理線程池配置


          停止線程

          終止線程有三種方式:

          (1)使用退出標(biāo)志,run()執(zhí)行完以后退出【拋出異常或者return】

          (2)使用stop強(qiáng)行停止線程,不推薦,會(huì)導(dǎo)致當(dāng)前任務(wù)執(zhí)行到一半突然中斷,出現(xiàn)不可預(yù)料的問(wèn)題;而且stop和suspend以及resume一樣是過(guò)期作廢的方法

          (3)使用interrupt中斷線程

          interrupt()方法不會(huì)真的停止線程,而是會(huì)記錄一個(gè)標(biāo)志,這個(gè)標(biāo)志,可以由下面的兩個(gè)方法檢測(cè)到。

          • Thread.interrupted( ) 測(cè)試當(dāng)前線程是否停止,但是它具有清除線程中斷狀態(tài)功能,如第一次返回true,第二次調(diào)用會(huì)返回false;

          • Thread.isInterrupted( ),僅返回結(jié)果,不清除狀態(tài)。重復(fù)調(diào)用會(huì)結(jié)果一致。

          基于上面的邏輯,可以根據(jù)標(biāo)志來(lái)在run()里面狀態(tài),然后再使用interrupt()來(lái)使代碼停止,停止代碼可以使用拋出異常的方式。

          如果在sleep里面拋出異常停止線程,會(huì)進(jìn)入catch,并清除停止?fàn)顟B(tài),使之變成false;

          stop()暴力停止,已經(jīng)被作廢,建議不使用;

          使用stop的方法帶來(lái)的問(wèn)題:

          1.執(zhí)行到一半強(qiáng)制停止,可能清理工作來(lái)不及;

          2.對(duì)鎖定的對(duì)象進(jìn)行了解鎖,導(dǎo)致數(shù)據(jù)不同步,不一致。

          return方法停止線程:

          其實(shí)就是使用 打標(biāo)記+return 替換 打標(biāo)記+拋異常


          暫停線程與恢復(fù)線程

          suspend()暫停,resume()恢復(fù),已經(jīng)被棄用,

          缺點(diǎn):

          1. 獨(dú)占,使用不當(dāng)很容易讓公共的同步對(duì)象獨(dú)占,使得其他線程無(wú)法訪問(wèn)。

          2. 不同步:線程暫停容易導(dǎo)致不同步。

          yield():作用是放棄當(dāng)前cpu資源,將他讓給其他任務(wù)去占用cpu;但是放棄的時(shí)間不確定,有可能剛放棄,馬上又獲得cpu時(shí)間片;直接在run方法里面使用即可。 線程優(yōu)先級(jí)

          多個(gè)線程可以設(shè)置優(yōu)先級(jí)。

          優(yōu)先級(jí)設(shè)置:

          setPriority( ) 方法;分為1-10,10個(gè)等級(jí),超過(guò)這個(gè)范圍,會(huì)拋出異常。

          java線程優(yōu)先級(jí)可以繼承,A線程啟動(dòng)B線程,那么B與A的優(yōu)先級(jí)是一樣的。

          優(yōu)先級(jí)高的絕大多數(shù)會(huì)先執(zhí)行,但結(jié)果不是百分之百的。


          對(duì)象以及變量訪問(wèn)

          在run里面執(zhí)行的方法,如果是同步的,則不會(huì)有線程安全問(wèn)題,使用synchronized關(guān)鍵字即可保證同步。

          synchronized持有的鎖是對(duì)象鎖,如果多個(gè)線程訪問(wèn)多個(gè)對(duì)象,則JVM會(huì)創(chuàng)建多個(gè)鎖。【多個(gè)對(duì)象,多個(gè)鎖,此處對(duì)象是指加了synchronized關(guān)鍵字的方法所在的類也就是創(chuàng)建線程時(shí)傳入的對(duì)象,例如:

          Thread a = new Thread(object1);

          Thread b= new Thread(object2);

          a.start();

          b.start();

          這種情況下線程a和b持有的是兩個(gè)不同的鎖。


          贓讀

          讀取全局變量時(shí),此變量已經(jīng)被其他線程修改過(guò)了,就會(huì)出現(xiàn)贓讀。


          synchronized 實(shí)際上是對(duì)象鎖

          現(xiàn)有A,B兩個(gè)線程,C對(duì)象,C擁有加了synchronized關(guān)鍵字的方法X1()和X2(),以及未加synchronized關(guān)鍵字的X3()方法。

          當(dāng)A線程訪問(wèn)X1方法時(shí),B線程想訪問(wèn)X1,必須等待A執(zhí)行完,釋放對(duì)象鎖;

          當(dāng)A在訪問(wèn)X1,B想訪問(wèn)X3(),無(wú)需等待,直接訪問(wèn)。

          當(dāng)A在訪問(wèn)X1,B想訪問(wèn)X2(),需要等待A執(zhí)行完。


          synchronized 鎖重入

          在synchronized方法內(nèi),調(diào)用本類的其他的synchronized方法時(shí),總是可以成功。

          如果不可重入的話,會(huì)造成死鎖;

          可重入鎖,支持在父子類繼承的環(huán)境:子類可以通過(guò)"可重入鎖"調(diào)用父類的同步方法。


          異常會(huì)釋放鎖

          當(dāng)一個(gè)線程執(zhí)行出現(xiàn)異常,會(huì)釋放他所持有的所有鎖。


          同步不具有繼承性

          父類中A()方法是synchronized的,子類中的A方法,不會(huì)是同步的,需要手動(dòng)加上。


          synchronized 同步語(yǔ)句塊

          synchronized(this){   //...同步的代碼塊...}

          synchronized聲明方法的弊端:

          A線程調(diào)用同步方法執(zhí)行長(zhǎng)時(shí)間任務(wù)時(shí),B線程需要等待很久。

          解決辦法:可以使用synchronized同步語(yǔ)句塊。

          synchronized可以修飾代碼塊。使用synchronized修飾需要保持同步部分代碼,其余部分異步,借此提高運(yùn)行效率。


          synchronized 代碼塊間的同步性

          A對(duì)象,擁有X1和X2兩個(gè)synchronized 同步代碼塊,

          那么,B線程在訪問(wèn)X1時(shí),C線程也無(wú)法訪問(wèn)X2,需要等待B線程釋放對(duì)象鎖。

          此處與synchronized 修飾方法時(shí)一樣。他們持有的都是對(duì)象鎖。


          任意對(duì)象作為監(jiān)視器

          synchronized 修飾的代碼塊時(shí),如果傳入this,則會(huì)監(jiān)視當(dāng)前對(duì)象,加鎖時(shí)會(huì)對(duì)當(dāng)前整個(gè)對(duì)象加鎖;

          例如:對(duì)象A有方法X1() 和X2() ,如果在X1和X2里有一段同步代碼塊,并且synchronized(this)傳入的都是this對(duì)象,那么在B線程訪問(wèn)X1的同步代碼塊時(shí),C線程也無(wú)法X2的同步代碼塊。

          如果傳入的不是this,而是另外的對(duì)象,則C可以訪問(wèn)X2的同步代碼塊。

          要保證傳入其他監(jiān)視對(duì)象時(shí)的成功同步,必須保證在調(diào)用時(shí),監(jiān)視對(duì)象是一致的,不能每次都new一個(gè)監(jiān)視對(duì)象,否則會(huì)導(dǎo)致變成異步的。


          臟讀問(wèn)題

          有時(shí)候,僅僅使用synchronized 修飾方法,并不能保證正確的邏輯。

          比如,兩個(gè)synchronized 修飾的方法add() 與getSize() ,他們分別是對(duì)list進(jìn)行讀與寫的操作,此時(shí)兩個(gè)線程先后調(diào)用這兩個(gè)方法,會(huì)導(dǎo)致結(jié)果超出預(yù)期。

          解決:

          add()方法中,synchronized 改成去修飾代碼塊,并且傳入監(jiān)視對(duì)象list;

          synchronized(list){   //--- add  ---}


          靜態(tài)同步synchronized 方法,與synchronized(class)代碼塊

          synchronized 加在static 靜態(tài)方法上,就是對(duì)當(dāng)前.java文件對(duì)應(yīng)的class類進(jìn)行持鎖。

          synchronized static等同于synchronized (object.class) 可以對(duì)該類的所有對(duì)象起作用,即:即使需要new不同的對(duì)象,也可以保持同步。


          String 的常量池特性

          一般不使用String變量來(lái)作為鎖的監(jiān)視對(duì)象,當(dāng)對(duì)一個(gè)String變量持有鎖時(shí),如果兩個(gè)訪問(wèn)線程傳入的String變量值一樣,會(huì)導(dǎo)致鎖不被釋放,其中一個(gè)線程無(wú)法執(zhí)行。

          可以使用對(duì)象來(lái)存儲(chǔ)相應(yīng)的變量解決此問(wèn)題。


          volatile 關(guān)鍵字

          一旦一個(gè)共享變量(類的成員變量、類的靜態(tài)成員變量)被volatile 修飾之后,那么就具備了兩層語(yǔ)義:

          1)保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見(jiàn)性,即一個(gè)線程修改了某個(gè)變量的值,這新值對(duì)其他線程來(lái)說(shuō)是立即可見(jiàn)的。

          2)禁止進(jìn)行指令重排序。


          volatile 保證有序性

          volatile關(guān)鍵字禁止指令重排序有兩層意思:

          1)當(dāng)程序執(zhí)行到volatile變量的讀操作或者寫操作時(shí),在其前面的操作的更改肯定全部已經(jīng)進(jìn)行,且結(jié)果已經(jīng)對(duì)后面的操作可見(jiàn);在其后面的操作肯定還沒(méi)有進(jìn)行;

          2)在進(jìn)行指令優(yōu)化時(shí),不能將在對(duì)volatile變量的讀操作或者寫操作的語(yǔ)句放在其后面執(zhí)行,也不能把volatile變量后面的語(yǔ)句放到其前面執(zhí)行。

          可能上面說(shuō)的比較繞,舉個(gè)簡(jiǎn)單的例子:

          //x、y為非volatile變量//flag為volatile變量x = 2;        //語(yǔ)句1y = 0;        //語(yǔ)句2flag = true;  //語(yǔ)句3x = 4;        //語(yǔ)句4y = -1;       //語(yǔ)句5

          由于flag變量為volatile變量,那么在進(jìn)行指令重排序的過(guò)程的時(shí)候,不會(huì)將語(yǔ)句3放到語(yǔ)句1、語(yǔ)句2前面,也不會(huì)講語(yǔ)句3放到語(yǔ)句4、語(yǔ)句5后面。但是要注意語(yǔ)句1和語(yǔ)句2的順序、語(yǔ)句4和語(yǔ)句5的順序是不作任何保證的。

          并且volatile關(guān)鍵字能保證,執(zhí)行到語(yǔ)句3時(shí),語(yǔ)句1和語(yǔ)句2必定是執(zhí)行完畢了的,且語(yǔ)句1和語(yǔ)句2的執(zhí)行結(jié)果對(duì)語(yǔ)句3、語(yǔ)句4、語(yǔ)句5是可見(jiàn)的。

          那么我們回到前面舉的一個(gè)例子:

          //線程1:context =loadContext();   //語(yǔ)句1inited = true;             //語(yǔ)句2
          //線程2:while(!inited ){ sleep();}
          doSomethingwithconfig(context);

          前面舉這個(gè)例子的時(shí)候,提到有可能語(yǔ)句2會(huì)在語(yǔ)句1之前執(zhí)行,那么久可能導(dǎo)致context還沒(méi)被初始化,而線程2中就使用未初始化的context去進(jìn)行操作,導(dǎo)致程序出錯(cuò)。

          這里如果用volatile關(guān)鍵字對(duì)inited變量進(jìn)行修飾,就不會(huì)出現(xiàn)這種問(wèn)題了,因?yàn)楫?dāng)執(zhí)行到語(yǔ)句2時(shí),必定能保證context已經(jīng)初始化完畢。


          與 synchronized 對(duì)比

          1. volatile是線程同步的輕量實(shí)現(xiàn),只能修飾變量,性能高于synchronized

          2. volatile保證可見(jiàn)性,不保證原子性【一旦其修飾的變量改變,其余的線程都能發(fā)現(xiàn),因?yàn)闀?huì)強(qiáng)制從公共堆棧取值】,synchronized保證原子性,間接保證可見(jiàn)性,因?yàn)樗麜?huì)將私有內(nèi)存和公共內(nèi)存的值同步

          例如:i++操作,實(shí)際上不是原子操作,他有3步:

          (1).從內(nèi)存取i值

          (2).計(jì)算i的值

          (3).將i的新值寫到內(nèi)存

          多個(gè)線程執(zhí)行時(shí),使用volatile,可能導(dǎo)致數(shù)據(jù)臟讀,進(jìn)而出現(xiàn)錯(cuò)誤。

          1. 多線程訪問(wèn)volatile不會(huì)阻塞,而synchronized會(huì)

          2. volatile是解決變量在多個(gè)線程之間的可見(jiàn)性,synchronized是保證多個(gè)線程之間資源的同步性。


          volatile 的實(shí)現(xiàn)原理

          1.可見(jiàn)性

          處理器為了提高處理速度,不直接和內(nèi)存進(jìn)行通訊,而是將系統(tǒng)內(nèi)存的數(shù)據(jù)獨(dú)到內(nèi)部緩存后再進(jìn)行操作,但操作完后不知什么時(shí)候會(huì)寫到內(nèi)存。

          如果對(duì)聲明了volatile變量進(jìn)行寫操作時(shí),JVM會(huì)向處理器發(fā)送一條Lock前綴的指令,將這個(gè)變量所在緩存行的數(shù)據(jù)寫會(huì)到系統(tǒng)內(nèi)存。這一步確保了如果有其他線程對(duì)聲明了volatile變量進(jìn)行修改,則立即更新主內(nèi)存中數(shù)據(jù)。

          但這時(shí)候其他處理器的緩存還是舊的,所以在多處理器環(huán)境下,為了保證各個(gè)處理器緩存一致,每個(gè)處理會(huì)通過(guò)嗅探在總線上傳播的數(shù)據(jù)來(lái)檢查 自己的緩存是否過(guò)期,當(dāng)處理器發(fā)現(xiàn)自己緩存行對(duì)應(yīng)的內(nèi)存地址被修改了,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無(wú)效狀態(tài),當(dāng)處理器要對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作時(shí),會(huì)強(qiáng)制重新從系統(tǒng)內(nèi)存把數(shù)據(jù)讀到處理器緩存里。這一步確保了其他線程獲得的聲明了volatile變量都是從主內(nèi)存中獲取最新的。

          2.有序性

          Lock前綴指令實(shí)際上相當(dāng)于一個(gè)內(nèi)存屏障(也成內(nèi)存柵欄),它確保指令重排序時(shí)不會(huì)把其后面的指令排到內(nèi)存屏障之前的位置,也不會(huì)把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時(shí),在它前面的操作已經(jīng)全部完成。


          volatile 的應(yīng)用場(chǎng)景

          synchronized關(guān)鍵字是防止多個(gè)線程同時(shí)執(zhí)行一段代碼,那么就會(huì)很影響程序執(zhí)行效率,而volatile關(guān)鍵字在某些情況下性能要優(yōu)于synchronized,但是要注意volatile關(guān)鍵字是無(wú)法替代synchronized關(guān)鍵字的,因?yàn)関olatile關(guān)鍵字無(wú)法保證操作的原子性。通常來(lái)說(shuō),使用volatile必須具備以下2個(gè)條件:

          1)對(duì)變量的寫操作不依賴于當(dāng)前值

          2)該變量沒(méi)有包含在具有其他變量的不變式中

          下面列舉幾個(gè)Java中使用volatile的幾個(gè)場(chǎng)景:

          ①.狀態(tài)標(biāo)記量

          volatile booleanflag = false;
          //線程1while(!flag){ doSomething();}
          //線程2public voidsetFlag() { flag = true;}

          根據(jù)狀態(tài)標(biāo)記,終止線程。

          ②.單例模式中的doublecheck

          class Singleton {     private volatile static Singleton instance= null;
          private Singleton() {}
          public static Singleton getInstance() {
          if(instance==null) { synchronized (Singleton.class) { if(instance==null) instance = new Singleton(); } } return instance;
          }}

          為什么要使用volatile 修飾instance?

          主要在于instance= new Singleton()這句,這并非是一個(gè)原子操作,事實(shí)上在 JVM 中這句話大概做了下面 3 件事情:

          1.給 instance 分配內(nèi)存

          2.調(diào)用Singleton 的構(gòu)造函數(shù)來(lái)初始化成員變量

          3.將instance對(duì)象指向分配的內(nèi)存空間(執(zhí)行完這步 instance就為非 null 了)。

          但是在 JVM 的即時(shí)編譯器中存在指令重排序的優(yōu)化。也就是說(shuō)上面的第二步和第三步的順序是不能保證的,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2。如果是后者,則在 3 執(zhí)行完畢、2 未執(zhí)行之前,被線程二搶占了,這時(shí) instance已經(jīng)是非 null 了(但卻沒(méi)有初始化),所以線程二會(huì)直接返回 instance,然后使用,然后順理成章地報(bào)錯(cuò)。


          線程間通信

          1、等待通知機(jī)制

          wait 使線程暫停,而notify 使線程繼續(xù)運(yùn)行。還有notifyAll() 方法。

          wait()和notify(),兩個(gè)方法來(lái)實(shí)現(xiàn)等待通知機(jī)制;

          注意:(1)兩個(gè)方法在調(diào)用時(shí)都需要持有當(dāng)前對(duì)象的對(duì)象鎖,所以都只能在同步代碼塊或者同步方法里面調(diào)用,如果不是會(huì)拋出異常。

          2、wait

          (2)wait方法會(huì)將當(dāng)前線程置入“預(yù)執(zhí)行隊(duì)列”,并在wait()所在代碼行停止執(zhí)行,直到接到notify(),或者被中斷;

          (3)執(zhí)行wait()后,當(dāng)前線程釋放鎖;

          3、notify

          (1)如果多個(gè)線程在wait,那么會(huì)由線程規(guī)劃器,挑選一個(gè)執(zhí)行notify,并使他獲取該對(duì)象的對(duì)象鎖;

          (2)noitfy執(zhí)行之后,當(dāng)前線程不會(huì)立馬釋放該對(duì)象鎖,wait狀態(tài)的線程也不能立馬獲得該對(duì)象鎖,要等執(zhí)行notify()方法的線程將程序執(zhí)行完,也就是退出synchronized代碼塊之后才會(huì)釋放鎖,并讓wait獲得。

          (3)多個(gè)wait的線程,第一個(gè)獲取到notify并執(zhí)行完之后,其余的wait狀態(tài)的線程如果沒(méi)有被通知,還是會(huì)一直阻塞。


          wait 之后自動(dòng)釋放鎖,notify 之后不會(huì)立馬釋放鎖

          interrupt 方法與 wait

          當(dāng)線程在wait狀態(tài)時(shí),調(diào)用對(duì)象的interrupt()方法,會(huì)拋出異常。

          (1)執(zhí)行完同步代碼塊之后,會(huì)釋放當(dāng)前對(duì)象的鎖

          (2)執(zhí)行同步代碼塊過(guò)程中,拋出異常也會(huì)釋放鎖

          (3)執(zhí)行wait()之后,也會(huì)釋放鎖

          wait(long)

          執(zhí)行wait(5000)后,首先會(huì)等待5秒,如果5秒內(nèi)沒(méi)有收到通知,會(huì)自動(dòng)喚醒線程,退出wait狀態(tài)。


          通過(guò)管道進(jìn)行線程間通信

          4個(gè)類進(jìn)行線程間通信:

          (1)字節(jié)流:PipedInputStream和PipedOuputStream

          (2)字符流:PipedReader和PipedWriter

          使用語(yǔ)法:

          輸出:PipedOuputStream

          PipedOuputStream out;

          out.write();

          out.close();


          join 方法

          在主線程中調(diào)用子線程的join方法,可以讓主線程等待子線程結(jié)束之后,

          再開(kāi)始執(zhí)行join()之后的代碼。

          join可以使線程排隊(duì)運(yùn)行,類似于synchronized的同步;區(qū)別在于join在內(nèi)部使用wait()等待,而synchronized使用對(duì)象監(jiān)視器原理同步。

          注意

          在join過(guò)程中,如果當(dāng)前線程對(duì)象被中斷,則當(dāng)前線程出現(xiàn)異常,子線程會(huì)繼續(xù)運(yùn)行;

          join(long)

          long參數(shù)是設(shè)定等待時(shí)間,使用sleep(long)也可以等待,但二者是有區(qū)別的:

          join(long),內(nèi)部是使用的wait(long),等待時(shí)會(huì)釋放鎖;

          sleep(long)等待時(shí)不會(huì)釋放鎖。


          ThreadLocal

          變量值的共享可以使用public static;

          如果想讓每個(gè)線程都有自己的共享變量。可以使用ThreadLocal;ThreadLocal可以看做全局存放數(shù)據(jù)的盒子,盒子中可以存儲(chǔ)每個(gè)線程的私有數(shù)據(jù);

          使用時(shí),只需新建一個(gè)類繼承ThreadLocal即可實(shí)現(xiàn),不同的線程在這個(gè)類中取到各自隔離的變量。


          InheritableThreadlocal

          InheritableThreadlocal可以在子線程中取得父線程繼承下來(lái)的值。

          使用注意:如果子線程取得值的同時(shí),主線程將值進(jìn)行了修改,那么取到的還是舊值。


          Lock 的使用

          ReenTrantLock可以和synchronized一樣實(shí)現(xiàn)多線程之間的同步互斥,ReenTrantLock類在功能上還更加強(qiáng)大,有嗅探鎖定,多路分支通知等。

          使用:

          privateLock lock =  new ReenTrantLock();try{    //加鎖    lock.lock();    //解鎖    lock.unlock();}catch(Exception e){    }


          ReenTrantLock 結(jié)合 Condition 實(shí)現(xiàn)等待/通知

          功能上與synchronized結(jié)合wait/notify一樣,而且更加靈活;

          一個(gè)Lock對(duì)象可以創(chuàng)建多個(gè)Condition(即對(duì)象監(jiān)視器)實(shí)例,線程對(duì)象可以注冊(cè)在指定的condition中,從而可以有選擇性的進(jìn)行線程通知,在線程調(diào)度上更加靈活。

          而在wait/notify時(shí),被通知的線程是JVM 隨機(jī)選擇的,不如ReenTrantLock 來(lái)得靈活。

          synchronized相當(dāng)于整個(gè)lock對(duì)象中只有一個(gè)單一的condition,所有的線程都注冊(cè)在它上面,線程開(kāi)始notify時(shí),需要通知所有的waitting線程,沒(méi)有選擇權(quán),效率不高。

          使用之前,必須使用lock.lock()獲取對(duì)象鎖。

          private Condition condition = lock.newCondition();try{ condition.await();}catch(Exception e){    }

          其實(shí)使用上:wait()/notify()/notifyAll()相當(dāng)于Condition類

          里面的await()/signal()/signalAll()

          wait(long timeout)相當(dāng)于await(long time,TimeUnit unit)


          推薦閱讀

          1、Spring Boot+Vue項(xiàng)目實(shí)戰(zhàn)

          2、B站:4小時(shí)上手MyBatis Plus

          3、一文搞懂前后端分離

          4、快速上手Spring Boot+Vue前后端分離


          楠哥簡(jiǎn)介

          資深 Java 工程師,微信號(hào) nnsouthwind

          《Java零基礎(chǔ)實(shí)戰(zhàn)》一書(shū)作者

          騰訊課程官方 Java 面試官今日頭條認(rèn)證大V

          GitChat認(rèn)證作者,B站認(rèn)證UP主(楠哥教你學(xué)Java)

          致力于幫助萬(wàn)千 Java 學(xué)習(xí)者持續(xù)成長(zhǎng)。




          有收獲,就點(diǎn)個(gè)在看?
          瀏覽 38
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久热最新视频在线观看 | 国产日韩视频在线 | 黄色电影在线观看国内免费 | 黄色网址五月天 | 精品人妻无码系列 |