<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>

          java多線程與高并發(fā):LockSupport、淘寶面試題與源碼閱讀方法論

          共 22764字,需瀏覽 46分鐘

           ·

          2022-06-08 15:43

          前言

          首先我們簡(jiǎn)單回顧一下前面三節(jié)課講的內(nèi)容,分別有線程的基本概念、synchronized、volatile、AtomicXXX、各種JUC同步框架(ReentrantLock、CountDownLatch、CyclicBarrier、Phaser、ReadWriteLock-StampedLock、Semaphore、Exchanger、LockSupport),其中synchornized重點(diǎn)講了一下,包括有synchornized的底層實(shí)現(xiàn)原理、鎖升級(jí)的概念(四種狀態(tài):無鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖),volatile我們講了可見性和禁止指令重排序如何實(shí)現(xiàn)。

          synchronized和ReentrantLock的不同?

          synchronized:系統(tǒng)自帶、系統(tǒng)自動(dòng)加鎖,自動(dòng)解鎖、不可以出現(xiàn)多個(gè)不同的等待隊(duì)列、默認(rèn)進(jìn)行四種鎖狀態(tài)的升級(jí)。

          ReentrantLock:需要手動(dòng)枷鎖,手動(dòng)解鎖、可以出現(xiàn)多個(gè)不同的等待隊(duì)列、CIS的實(shí)現(xiàn)本章我們補(bǔ)一個(gè)小漏洞,它叫LockSupport,然后我們分析兩道面試題,緊接著我會(huì)教大家閱讀源碼的技巧,源碼層出不窮,生生不息,掌握了源碼的閱讀技巧,大家培養(yǎng)出了閱讀源碼興趣的時(shí)候,之后好多代碼,你需要自己去摳,摳出來才是你自己的,最后我們會(huì)分析AQS源碼,以上是我們本章主講的內(nèi)容概述。

          LockSupport

          我們會(huì)以幾個(gè)小程序?yàn)榘咐归_對(duì)LockSupport的講解,在以前我們要阻塞和喚醒某一個(gè)具體的線程有很多限制比如:

          1、因?yàn)閣ait()方法需要釋放鎖,所以必須在synchronized中使用,否則會(huì)拋出異常
          IllegalMonitorStateException

          2、notify()方法也必須在synchronized中使用,并且應(yīng)該指定對(duì)象

          3、synchronized()、wait()、notify()對(duì)象必須一致,一個(gè)synchronized()代碼塊中只能有一個(gè)線程調(diào)用wait()或notify()以上諸多限制,體現(xiàn)出了很多的不足,所以LockSupport的好處就體現(xiàn)出來了。

          在JDK1.6中的java.util.concurrent的子包locks中引了LockSupport這個(gè)API,LockSupport是一個(gè)比較底層的工具類,用來創(chuàng)建鎖和其它同步工具類的基本線程阻塞原語。java鎖和同步器框架的核心 AQS:


          AbstractQueuedSynchronizer,就是通過調(diào)用 LockSupport .park()和 LockSupport .unpark()的方法,來實(shí)現(xiàn)線程的阻塞和喚醒的。我們先來看一個(gè)小程序:

          public class T13_TestLockSupport {public static void main(String[] args) {//使用lombda表達(dá)式創(chuàng)建一個(gè)線程tThread t = new Thread(()->{for (int i = 0; i < 10; i++) {
          System.out.println(i);if(i == 5) {//使用LockSupport的park()方法阻塞當(dāng)前線程tLockSupport.park();
          }try {//使當(dāng)前線程t休眠1秒TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          });//啟動(dòng)當(dāng)前線程tt.start();
          }
          }

          從以上的小程序中,我們不難看出LockSupport使用起來的是比較靈靈活的,沒有了所謂的限制。我們來分析一下代碼的執(zhí)行過程,首先我們使用lombda表達(dá)式創(chuàng)建了線程對(duì)象 " t " ,然后通過 " t " 對(duì)象調(diào)用線程的啟動(dòng)方法start(),然后我們?cè)倏淳€程的內(nèi)容,在for循環(huán)中,當(dāng) i 的值等于5的時(shí)候,我們調(diào)用了LockSupport的.park()方法使當(dāng)前線程阻塞,注意看方法并沒有加鎖,就默認(rèn)使當(dāng)前線程阻塞了,由此可以看出LockSupprt.park()方法并沒有加鎖的限制。

          我們?cè)賮砜匆粋€(gè)小程序:

          public class T13_TestLockSupport {public static void main(String[] args) {//使用lombda表達(dá)式創(chuàng)建一個(gè)線程tThread t = new Thread(()->{for (int i = 0; i < 10; i++) {
          System.out.println(i);if(i == 5) {//使用LockSupport的park()方法阻塞當(dāng)前線程tLockSupport.park();
          }try {//使當(dāng)前線程t休眠1秒TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          });//啟動(dòng)當(dāng)前線程tt.start();//喚醒線程tLockSupport.unpark(t);
          }
          }

          我們來分析一下以上小程序,我們只需要在第一個(gè)小程序的主線程中,調(diào)用LockSupport的unpark()方法,就可以喚醒某個(gè)具體的線程,這里我們指定了線程 " t " ,代碼運(yùn)行以后結(jié)果顯而易見,線程并沒有被阻塞,我們成功喚醒了線程 " t " ,在這里還有一點(diǎn),需要我們來分析一下,在主線程中線程 " t " 調(diào)用了start()方法以后,因?yàn)榫o接著執(zhí)行了LockSupport的unpark()方法,所以也就是說,在線程 " t "還沒有執(zhí)行還沒有被阻塞的時(shí)候,已經(jīng)調(diào)用了LockSupport的unpark()方法來喚醒線程 " t " ,之后線程 " t"才啟動(dòng)調(diào)用了LockSupport的park()來使線程 " t " 阻塞,但是線程 " t " 并沒有被阻塞,由此可以看出,LockSupport的unpark()方法可以先于LockSupport的park()方法執(zhí)行。

          我們?cè)賮砜醋詈笠粋€(gè)小程序:

          public class T13_TestLockSupport {public static void main(String[] args) {//使用lombda表達(dá)式創(chuàng)建一個(gè)線程tThread t = new Thread(()->{for (int i = 0; i < 10; i++) {
          System.out.println(i);if(i == 5) {//調(diào)用LockSupport的park()方法阻塞當(dāng)前線程tLockSupport.park();
          }if(i == 8){//調(diào)用LockSupport的park()方法阻塞當(dāng)前線程tLockSupport.park();
          }try {//使當(dāng)前線程t休眠1秒TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          });//啟動(dòng)當(dāng)前線程tt.start();//喚醒線程tLockSupport.unpark(t);
          }
          }

          我們來分析一下以上小程序,在第二個(gè)小程序的基礎(chǔ)上又添加了一個(gè)if判斷,在i等于8的時(shí)候再次調(diào)用LockSupport的park()方法來使線程 " t " 阻塞, 我們可以看到線程被阻塞了,原因是LockSupport的unpark()方法就像是獲得了一個(gè)“令牌”,而LockSupport的park()方法就像是在識(shí)別“令牌”,當(dāng)主線程調(diào)用了LockSupport.unpark(t)方法也就說明線程 " t " 已經(jīng)獲得了”令牌”,當(dāng)線程 " t " 再調(diào)用LockSupport的park()方法時(shí),線程 " t " 已經(jīng)有令牌了,這樣他就會(huì)馬上再繼續(xù)運(yùn)行,也就不會(huì)被阻塞了,但是當(dāng)i等于8的時(shí)候線程 " t " 再次調(diào)用了LockSupport的park()方法使線程再次進(jìn)入阻塞狀態(tài),這個(gè)時(shí)候“令牌”已經(jīng)被使用作廢掉了,也就無法阻塞線程 " t " 了,而且如果主線程處于等待“令牌”狀態(tài)時(shí),線程 " t " 再次調(diào)用了LockSupport的park()方法,那么線程 " t "就會(huì)永遠(yuǎn)阻塞下去,即使調(diào)用unpark()方法也無法喚醒了。

          由以上三個(gè)小程序我們可以總結(jié)得出以下幾點(diǎn):

          1、LockSupport不需要synchornized加鎖就可以實(shí)現(xiàn)線程的阻塞和喚醒

          2、LockSupport.unpartk()可以先于LockSupport.park()執(zhí)行,并且線程不會(huì)阻塞

          3、如果一個(gè)線程處于等待狀態(tài),連續(xù)調(diào)用了兩次park()方法,就會(huì)使該線程永遠(yuǎn)無法被喚醒

          LockSupport中park()和unpark()方法的實(shí)現(xiàn)原理

          park()和unpark()方法的實(shí)現(xiàn)是由Unsefa類提供的,而Unsefa類是由C和C++語言完成的,其實(shí)原理也是比較好理解的,它主要通過一個(gè)變量作為一個(gè)標(biāo)識(shí),變量值在0,1之間來回切換,當(dāng)這個(gè)變量大于0的時(shí)候線程就獲得了“令牌”,從這一點(diǎn)我們不難知道,其實(shí)park()和unpark()方法就是在改變這個(gè)變量的值,來達(dá)到線程的阻塞和喚醒的,具體實(shí)現(xiàn)不做贅述了。

          淘寶面試題

          面試題1

          實(shí)現(xiàn)一個(gè)容器,提供兩個(gè)方法add、size,寫兩個(gè)線程:

          線程1,添加10個(gè)元素到容器中

          線程2,實(shí)時(shí)監(jiān)控元素個(gè)數(shù),當(dāng)個(gè)數(shù)到5個(gè)時(shí),線程2給出提示并結(jié)束

          我們根據(jù)小程序來剖析這個(gè)面試題,小程序1:

          小程序1的執(zhí)行流程:

          通過內(nèi)部的list來new一個(gè)ArrayList,在自定義的add方法直接調(diào)用list的add方法,在自定義的size方法直接調(diào)用list的size方法,想法很簡(jiǎn)單,首先小程序化了這個(gè)容器,接下來啟動(dòng)了t1線程,t1線程中做了一個(gè)循環(huán),每次循環(huán)就添加一個(gè)對(duì)象,加一個(gè)就打印顯示一下到第幾個(gè)了,然后給了1秒的間隔,在t2線程中寫了了一個(gè)while循環(huán),實(shí)時(shí)監(jiān)控著集合中對(duì)象數(shù)量的變化,如果數(shù)量達(dá)到5就結(jié)束t2線程。

          小程序1的執(zhí)行結(jié)果:

          方法并沒有按預(yù)期的執(zhí)行,我們注意看t2線程中c.size()這個(gè)方法,當(dāng)對(duì)象添加以后,ArraylList的size()方肯定是要更新的,我們分析一下,當(dāng)t1線程中的size()方法要更新的時(shí)候,還沒有更新t2線程就讀了,這個(gè)時(shí)候t2線程讀到的值就與實(shí)際當(dāng)中加入的值不一致了,所以得出兩結(jié)論,第一這個(gè)方案沒有加同步,第二while(true)中的c.size()方法永遠(yuǎn)沒有檢測(cè)到,沒有檢測(cè)到的原因是線程與線程之間是不可見的

          public class T01_WithoutVolatile {
          List lists = new ArrayList();public void add(Object o) {
          lists.add(o);
          }public int size() {return lists.size();
          }public static void main(String[] args) {
          T01_WithoutVolatile c = new T01_WithoutVolatile();new Thread(() -> {for(int i=0; i<10; i++) {
          c.add(new Object());
          System.out.println("add " + i);try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          }, "t1").start();new Thread(() -> {while(true) {if(c.size() == 5) {javabreak;
          }
          }
          System.out.println("t2 結(jié)束");
          }, "t2").start();
          }
          }
          小程序2public class T02_WithVolatile {volatile List lists = new LinkedList();public void add(Object o) {
          lists.add(o);
          }public int size() {return lists.size();
          }public static void main(String[] args) {
          T02_WithVolatile c = new T02_WithVolatile();new Thread(() -> {for(int i=0; i<10; i++) {
          c.add(new Object());
          System.out.println("add " + i);/*try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }*/
          }
          }, "t1").start();new Thread(() -> {while(true) {if(c.size() == 5) {break;
          }
          }
          System.out.println("t2 結(jié)束");
          }, "t2").start();
          }
          }

          小程序2是在小程序1的基礎(chǔ)上做了一些改動(dòng),用volatile修飾了一下List集合,實(shí)現(xiàn)線程間信息的傳遞,但是還是有不足之處,程序還是無法運(yùn)行成功,而且我們還得出,volatile一定要盡量去修飾普通的值,不要去修飾引用值,因?yàn)関olatile修飾引用類型,這個(gè)引用對(duì)象指向的是另外一個(gè)new出來的對(duì)象對(duì)象,如果這個(gè)對(duì)象里邊的成員變量的值改變了,是無法觀察到的,所以小程序2也是不理想的。

          小程序3:

          public class T03_NotifyHoldingLock { //wait notify//添加volatile,使t2能夠得到通知volatile List lists = new ArrayList();public void add(Object o) {
          lists.add(o);
          }public int size() {return lists.size();
          }public static void main(String[] args) {
          T03_NotifyHoldingLock c = new T03_NotifyHoldingLock();
          final Object lock = new Object();//需要注意先啟動(dòng)t2再啟動(dòng)t1new Thread(() -> {
          synchronized(lock) {
          System.out.println("t2 啟動(dòng)");if(c.size() != 5) {try {lock.wait();
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          System.out.println("t2 結(jié)束");
          }
          }, "t2").start();try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e1) {
          e1.printStackTrace();
          }new Thread(() -> {
          System.out.println("t1 啟動(dòng)");
          synchronized(lock) {for(int i=0; i<10; i++) {
          c.add(new Object());
          System.out.println("add " + i);if(c.size() == 5) {lock.notify();
          }try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          }
          }, "t1").start();
          }
          }

          小程序3的執(zhí)行流程:

          小程序3用了鎖的方式(利用wait()和notify()),通過給object對(duì)象枷鎖然后調(diào)用wait()和notify()實(shí)現(xiàn)這道面試題,我們從頭到尾分析一下,首先List集合實(shí)現(xiàn)的add和size方法不多做解釋,我們把重點(diǎn)放在mian方法上,main方法里我們創(chuàng)建了object對(duì)象,然后寫了兩個(gè)線程t1和t2,t1用來增加對(duì)象,t2用來監(jiān)控list集合添加的對(duì)象個(gè)數(shù),在t2線程我們給object對(duì)象加鎖,然后判斷l(xiāng)ist集合對(duì)象的個(gè)數(shù)為5的時(shí)候,就調(diào)用wait()方法阻塞t2線程,并給出相應(yīng)提示,t1線程里我們給object對(duì)象加鎖,通過for循環(huán)來給list集合添加對(duì)象,當(dāng)對(duì)象添加到5個(gè)的時(shí)候,喚醒t2線程來完成對(duì)象個(gè)數(shù)的監(jiān)控,這里我們需要保證先啟動(dòng)的是第二個(gè)線程,讓它直接進(jìn)入監(jiān)控狀態(tài),以完成實(shí)時(shí)監(jiān)控。

          小程序3的執(zhí)行結(jié)果:

          當(dāng)試過了小程序3,我們會(huì)發(fā)現(xiàn),這種寫法也是行不通的,原因是notify()方法不釋放鎖,當(dāng)t1線程調(diào)用了notify()方法后,并沒有釋放當(dāng)前的鎖,所以t1還是會(huì)執(zhí)行下去,待到t1執(zhí)行完畢,t2線程才會(huì)被喚醒接著執(zhí)行,這個(gè)時(shí)候?qū)ο笠呀?jīng)不只有5個(gè)了,所以這個(gè)方案也是行不通的。

          小程序4public class T03_NotifyHoldingLock { //wait notify//添加volatile,使t2能夠得到通知volatile List lists = new ArrayList();public void add(Object o) {
          lists.add(o);
          }public int size() {return lists.size();
          }public static void main(String[] args) {
          T03_NotifyHoldingLock c = new T03_NotifyHoldingLock();
          final Object lock = new Object();//需要注意先啟動(dòng)t2再啟動(dòng)t1new Thread(() -> {
          synchronized(lock) {
          System.out.println("t2 啟動(dòng)");if(c.size() != 5) {try {lock.wait();
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          System.out.println("t2 結(jié)束");
          }//通知t1繼續(xù)執(zhí)行lock.notify()。
          }, "t2").start();try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e1) {
          e1.printStackTrace();
          }new Thread(() -> {
          System.out.println("t1 啟動(dòng)");
          synchronized(lock) {for(int i=0; i<10; i++) {
          c.add(new Object());
          System.out.println("add " + i);if(c.size() == 5) {lock.notify();//釋放鎖,讓t2得以執(zhí)行try{lock.wait();
          }catch(InterruptedException e){
          e.printStackTrace();
          }
          }try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          }
          }, "t1").start();
          }
          }

          小程序4是在小程序3的基礎(chǔ)上做了一些小改動(dòng),我們來分析一下執(zhí)行流程,首先t2線程執(zhí)行,判斷到list集合里的對(duì)象數(shù)量沒有5個(gè),t2線程被阻塞了,接下來t1線程開始執(zhí)行,當(dāng)循環(huán)添加了5個(gè)對(duì)象后,喚醒了t2線程,重點(diǎn)在于小程序3我們說過notify()方法是不會(huì)是釋放鎖的,所以在notify()以后,又緊接著調(diào)用了wait()方法阻塞了t1線程,實(shí)現(xiàn)了t2線程的實(shí)時(shí)監(jiān)控,t2線程執(zhí)行結(jié)束,打印出相應(yīng)提示,最后調(diào)用notify()方法喚醒t1線程,讓t1線程完成執(zhí)行??催^執(zhí)行結(jié)果,發(fā)現(xiàn)示例4完成了面試題的功能成功運(yùn)行。

          小程序5:

          public class T05_CountDownLatch {//添加volatile,使t2能夠得到通知volatile List lists = new ArrayList();public void add(Object o) {
          lists.add(o);
          }public int size() {return lists.size();
          }public static void main(String[] args) {
          T05_CountDownLatch c = new T05_CountDownLatch();//需要注意先啟動(dòng)t2再啟動(dòng)t1CountDownLatch latch = new CountDownLatch(1);new Thread(() -> {
          System.out.println("t2 啟動(dòng)");if (c.size() != 5) {try {
          latch.await();
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          System.out.println("t2 結(jié)束");
          }, "t2").start();try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e1) {
          e1.printStackTrace();
          }new Thread(() -> {
          System.out.println("t1 啟動(dòng)");for (int i = 0; i < 10; i++) {
          c.add(new Object());
          System.out.println("add " + i);if (c.size() == 5) {// 暫停t1線程latch.countDown();
          }/*try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }*/
          }
          }, "t1").start();
          }
          }

          小程序5我們用CountDownLatch ( 門閂 ) 來完成這一題的需求,我們來分析代碼的執(zhí)行流程,首先我們不難看出和小程序4的寫法大同小異,同樣是list集合實(shí)現(xiàn)add和size方法,兩個(gè)線程t1和t2,t1線程里是循環(huán)添加對(duì)象,t2里是實(shí)時(shí)監(jiān)控,不同點(diǎn)在于沒有了鎖,采用了await()方法替換了t2線程和t1線程中的wait()方法,執(zhí)行流程是創(chuàng)建門閂對(duì)象latch,t2線程開始啟動(dòng),判斷到對(duì)象不等于5,調(diào)用await()方法阻塞t2線程,t1線程開始執(zhí)行添加對(duì)象,當(dāng)對(duì)象增加到5個(gè)時(shí),打開門閂讓t2繼續(xù)執(zhí)行。

          執(zhí)行結(jié)果看似沒什么大問題,但是當(dāng)我們把休眠1秒這段帶代碼,從t1線程里注釋掉以后,會(huì)發(fā)現(xiàn)出錯(cuò)了,原因是在t1線程里,對(duì)象增加到5個(gè)時(shí),t2線程的門閂確實(shí)被打開了,但是t1線程馬上又會(huì)接著執(zhí)行,之前是t1會(huì)休眠1秒,給t2線程執(zhí)行時(shí)間,但當(dāng)注釋掉休眠1秒這段帶代碼,t2就沒有機(jī)會(huì)去實(shí)時(shí)監(jiān)控了,所以這種方案來使用門閂是不可行的。但是如果我們非得使用門閂,還要求在對(duì)象數(shù)量為5的時(shí)候把t2線程打印出來,如何實(shí)現(xiàn)呢?

          小程序6:

          public class T05_CountDownLatch {//添加volatile,使t2能夠得到通知volatile List lists = new ArrayList();public void add(Object o) {
          lists.add(o);
          }public int size() {return lists.size();
          }public static void main(String[] args) {
          T05_CountDownLatch c = new T05_CountDownLatch();
          CountDownLatch latch = new CountDownLatch(1);//需要注意先啟動(dòng)t2再啟動(dòng)t1new Thread(() -> {
          System.out.println("t2 啟動(dòng)");if (c.size() != 5) {try {
          latch.await();
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          System.out.println("t2 結(jié)束");
          }, "t2").start();try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e1) {
          e1.printStackTrace();
          }new Thread(() -> {
          System.out.println("t1 啟動(dòng)");for (int i = 0; i < 10; i++) {
          c.add(new Object());System.out.println("add " + i);if (c.size() == 5) {//打開門閂,讓t2得以執(zhí)行latch.countDown();//給t1上門閂,讓t2有機(jī)會(huì)執(zhí)行try {
          latch.await();
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          }, "t1").start();
          }
          }

          小程序6很容易理解,我們只需要在t1線程打開t2線程門閂的時(shí)候,讓他再給自己加一個(gè)門閂就可以了。

          小程序7:

          public class T06_LockSupport {//添加volatile,使t2能夠得到通知volatile List lists = new ArrayList();public void add(Object o) {
          lists.add(o);
          }public int size() {return lists.size();
          }public static void main(String[] args) {
          T06_LockSupport c = new T06_LockSupport();
          CountDownLatch latch = new CountDownLatch(1);//需要注意先啟動(dòng)t2再啟動(dòng)t1Thread t2 = new Thread(() -> {
          System.out.println("t2 啟動(dòng)");if (c.size() != 5) {
          LockSupport.park();
          }
          System.out.println("t2 結(jié)束");
          LockSupport.unpark(t1);
          }, "t2");
          t2.start();try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e1) {
          e1.printStackTrace();
          }new Thread(() -> {
          System.out.println("t1 啟動(dòng)");for (int i = 0; i < 10; i++) {
          c.add(new Object());
          System.out.println("add " + i);if (c.size() == 5) {
          LockSupport.unpark(t2);
          LockSupport.park();
          }/*try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }*/
          }
          }, "t1").start();
          }
          }

          小程序7采用了本章所學(xué)的LockSupport實(shí)現(xiàn)的,與之前的小程序也是大同小異,不同的只是改變了線程阻塞和喚醒所使用的方法,仔細(xì)觀看小程序7就會(huì)明白,但是小程序7中其實(shí)也是有不足的,當(dāng)注釋掉t1線程中休眠1秒方法的時(shí)候,程序就出錯(cuò)了,原因是在t1線程調(diào)用unpark()方法喚醒t2線程的時(shí)候,t1線程并沒有停止,就會(huì)造成t2線程無法及時(shí)的打印出提示信息,怎么解決呢?很簡(jiǎn)單,在t1線程調(diào)用unpark()方法喚醒t2線程的時(shí)候,緊接著調(diào)用park()方法使t1線程阻塞,然后在t2線程打印信息結(jié)束后調(diào)用unpark()方法喚醒t1線程,至此程序結(jié)束,具體實(shí)現(xiàn),我們往下看。

          小程序8

          public class T07_LockSupport_WithoutSleep {// 添加volatile,使t2能夠得到通知volatile List lists = new ArrayList();public void add(Object o) {
          lists.add(o);
          }public int size() {return lists.size();
          }static Thread t1 = null, t2 = null;public static void main(String[] args) {
          T06_LockSupport c = new T06_LockSupport();
          CountDownLatch latch = new CountDownLatch(1);
          t2 = new Thread(() -> {
          System.out.println("t2 啟動(dòng)");if (c.size() != 5) {
          LockSupport.park();
          }
          System.out.println("t2 結(jié)束");
          LockSupport.unpark(t1);
          }, "t2");try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e1) {
          e1.printStackTrace();
          }
          t1 = new Thread(() -> {
          System.out.println("t1 啟動(dòng)");for (int i = 0; i < 10; i++) {
          c.add(new Object());
          System.out.println("add " + i);if (c.size() == 5) {
          LockSupport.unpark(t2);
          LockSupport.park();
          }try {
          TimeUnit.SECONDS.sleep(1);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          }, "t1");//需要注意先啟動(dòng)t2再啟動(dòng)t1t2.start();
          t1.start();
          }
          }

          我們來分析一下小程序8,首先看,我們?cè)陬惖某蓡T變量里定義了靜態(tài)的線程對(duì)象t1和t2,然后在main方法里創(chuàng)建了t1線程和t2線程,t2線程中判斷了list集合中對(duì)象的數(shù)量,然后t2線程阻塞,t1線程開始執(zhí)行添加對(duì)象,對(duì)象達(dá)到5個(gè)時(shí),打開t2線程阻塞t1線程,至此程序結(jié)束,運(yùn)行成功。

          小程序9:

          public class T08_Semaphore {// 添加volatile,使t2能夠得到通知volatile List lists = new ArrayList();public void add(Object o) {
          lists.add(o);
          }public int size() {return lists.size();
          }static Thread t1 = null, t2 = null;public static void main(String[] args) {
          T08_Semaphore c = new T08_Semaphore();
          Semaphore s = new Semaphore(1);
          t1 = new Thread(() -> {try {
          s.acquire();for (int i = 0; i < 5; i++) {
          c.add(new Object());
          System.out.println("add " + i);
          }
          s.release();
          } catch (InterruptedException e) {
          e.printStackTrace();
          }try {
          t2.start();
          t2.join();
          } catch (InterruptedException e) {
          e.printStackTrace();
          }try {
          s.acquire();for (int i = 5; i < 10; i++) {
          c.add(new Object());
          System.out.println("add"+i);
          }
          s.release();
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }, "t1");
          t2 = new Thread(() -> {try {
          s.acquire();
          System.out.println("t2 結(jié)束");
          s.release();
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }, "t2");
          t1.start();
          }
          }

          小程序9我們通過Semaphore來實(shí)現(xiàn),大體的執(zhí)行流程大體是這樣的,創(chuàng)建一個(gè)Semaphore對(duì)象,設(shè)置只能有1一個(gè)線程可以運(yùn)行,首先線程1開始啟動(dòng),調(diào)用acquire()方法限制其他線程運(yùn)行,在for循環(huán)添加了4個(gè)對(duì)象以后,調(diào)用s.release()表示其他線程可以運(yùn)行,這個(gè)時(shí)候t1線程啟動(dòng)t2線程,調(diào)用join()把CPU的控制權(quán)交給t2線程,t2線程打印出提示信息,并繼續(xù)輸出后來的對(duì)象添加信息,當(dāng)然了這個(gè)方案看起來很牽強(qiáng),但是的確實(shí)現(xiàn)了這個(gè)效果,思路是好的,可以用做參考。

          面試題1總結(jié)

          面試題1中的9個(gè)小程序9種方案,5種技術(shù)分別是volatile、wait()和notify()、Semaphore、CountDownLatch、LockSupport,其中wait()和notify()這個(gè)方案建議牢牢掌握,其它的可以用作鞏固技術(shù)。

          面試題2

          寫一個(gè)固定容量同步容器,擁有put和get方法,以及getCount方法,能夠支持2個(gè)生產(chǎn)者線程以及10個(gè)消費(fèi)者線程的阻塞調(diào)用。

          小程序1:

          public class MyContainer1<T> {final private LinkedList<T> lists = new LinkedList<>();final private int MAX = 10; //最多10個(gè)元素private int count = 0;//生產(chǎn)者public synchronized void put(T t) {while(lists.size() == MAX) { //想想為什么用while而不是if?try {this.wait(); //effective java} catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          lists.add(t);
          ++count;this.notifyAll(); //??

          知消費(fèi)者線程進(jìn)行消費(fèi)
          }//消費(fèi)者public synchronized T get() {
          T t = null;while(lists.size() == 0) {try {this.wait();
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          }
          t = lists.removeFirst();
          count --;this.notifyAll(); //通知生產(chǎn)者線程進(jìn)行生產(chǎn)return t;
          }public static void main(String[] args) {
          MyContainer1<String> c = new MyContainer1<>();//啟動(dòng)消費(fèi)者線程for(int i=0; i<10; i++) {new Thread(()->{for(int j=0; j<5; j++) System.out.println(c.get());
          }, "c" + i).start();
          }try {
          TimeUnit.SECONDS.sleep(2);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }//啟動(dòng)生產(chǎn)者線程for(int i=0; i<2; i++) {new Thread(()->{for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() +" " + j);
          }, "p" + i).start();
          }
          }
          }

          我們來說一下以上小程序的執(zhí)行流程,我們創(chuàng)建了一個(gè)LinkedList集合用于保存 “饅頭”,定義了MAX變量來限制饅頭的總數(shù),定義了count變量用來判斷生產(chǎn)了幾個(gè) “饅頭”和消費(fèi)了幾個(gè) “饅頭”,在put()方法中,首先判斷 LinkedList集合中“饅頭”是否是MAX變量的值,如果是啟動(dòng)所有消費(fèi)者線程,反之開始生產(chǎn) “饅頭”,在get()方法中,首先判斷是否還有 “饅頭”,也就是MAX的值是否為0,如果為0通知所有生產(chǎn)者線程開始生產(chǎn) “饅頭”,反之不為0 “饅頭”數(shù)就繼續(xù)減少,需要注意的點(diǎn)是,我們?yōu)槭裁匆觭ynchronized,因?yàn)槲覀?+count我們生產(chǎn)了3個(gè) “饅頭”,當(dāng)還沒來得及加的時(shí)候,count值為2的時(shí)候,另外一個(gè)線程讀到的值很可能是2,并不是3,所以不加鎖就會(huì)出問題,我們接著來看main方法中通過for循環(huán)分別創(chuàng)建了2個(gè)生產(chǎn)者線程生產(chǎn)分別生產(chǎn)25 “饅頭”,也就是50個(gè)饅頭,10個(gè)消費(fèi)者線程每個(gè)消費(fèi)者消費(fèi)5個(gè) “饅頭”,也就是50個(gè) “饅頭”,首先啟動(dòng)消費(fèi)者線程,然后啟動(dòng)生產(chǎn)者線程,至此流程介紹完畢。

          我們來分析一下這個(gè)小程序

          為什么用while而不是用if?因?yàn)楫?dāng)LinkedList集合中“饅頭”數(shù)等于最大值的時(shí)候,if在判斷了集合的大小等于MAX的時(shí)候,調(diào)用了wait()方法以后,它不會(huì)再去判斷一次,方法會(huì)繼續(xù)往下運(yùn)行,假如在你wait()以后,另一個(gè)方法又添加了一個(gè)“ 饅頭”,你沒有再次判斷,就又添加了一次,造成數(shù)據(jù)錯(cuò)誤,就會(huì)出問題,因此必須用while。

          注意看我們用的是notifyAll()來喚醒線程的,notifyAll()方法會(huì)叫醒等待隊(duì)列的所有方法,那么我們都知道,用了鎖以后就只有一個(gè)線程在運(yùn)行,其他線程都得wait(),不管你有多少個(gè)線程,這個(gè)時(shí)候被叫醒的線程有消費(fèi)者的線程和生產(chǎn)者的線程,所有的線程都會(huì)爭(zhēng)搶這把鎖,比如說我們是生產(chǎn)者線程,生產(chǎn)滿了,滿了以后我們叫醒消費(fèi)者線程,可是很不幸的是,它同樣的也會(huì)叫醒另外一個(gè)生產(chǎn)者線程,假如這個(gè)生產(chǎn)者線程難道了這把鎖剛才第一個(gè)生產(chǎn)者釋放的這把鎖,拿到了以后,它又wait()一遍,wait()完以后,又叫醒全部的線程,然后又開始爭(zhēng)搶這把鎖,其實(shí)從這個(gè)意義上來講,生產(chǎn)者的線程wait的你是沒有必要去叫醒別的生產(chǎn)者的,我們能不能只叫醒消費(fèi)者線程,就是生產(chǎn)者線程只叫醒消費(fèi)者線程,消費(fèi)者線程只負(fù)責(zé)叫醒生產(chǎn)者線程,如果想達(dá)到這樣一個(gè)程度的話用另外一個(gè)小程序。

          public class MyContainer2<T> {final private LinkedList<T> lists = new LinkedList<>();final private int MAX = 10; //最多10個(gè)元素private int count = 0;private Lock lock = new ReentrantLock();private Condition producer = lock.newCondition();private Condition consumer = lock.newCondition();public void put(T t) {try {
          lock.lock();while(lists.size() == MAX) { //想想為什么用while而不是用if?producer.await();
          }
          lists.add(t);
          ++count;
          consumer.signalAll(); //通知消費(fèi)者線程進(jìn)行消費(fèi)} catch (InterruptedException e) {
          e.printStackTrace();
          } finally {
          lock.unlock();
          }
          }public T get() {
          T t = null;try {
          lock.lock();while(lists.size() == 0) {
          consumer.await();
          }
          t = lists.removeFirst();
          count --;
          producer.signalAll(); //通知生產(chǎn)者進(jìn)行生產(chǎn)} catch (InterruptedException e) {
          e.printStackTrace();
          } finally {
          lock.unlock();
          }return t;
          }public static void main(String[] args) {
          MyContainer2<String> c = new MyContainer2<>();//啟動(dòng)消費(fèi)者線程for(int i=0; i<10; i++) {new Thread(()->{for(int j=0; j<5; j++) System.out.println(c.get());
          }, "c" + i).start();
          }try {
          TimeUnit.SECONDS.sleep(2);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }//啟動(dòng)生產(chǎn)者線程for(int i=0; i<2; i++) {new Thread(()->{for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() +" " + j);
          }, "p" + i).start();
          }
          }
          }

          上面這個(gè)小程序用了ReentrantLock,它與synchronized最大區(qū)別其實(shí)在這個(gè)面試題里已經(jīng)體現(xiàn)出來了,ReentrantLock它可以有兩種Condition條件,在put()方法里是我們的生產(chǎn)者線程,生產(chǎn)者線程lock()最后unlock()不多說,一旦MAX達(dá)到峰值的時(shí)候是producer.await(),最后是consumer.signalAll(),就是說我在producer的情況下阻塞的,我叫醒的時(shí)候只叫醒consumer,在get()方法里是我們的消費(fèi)者線程,一旦集合的size空了,我是consumer.await(),然后我只叫醒producer,這就是ReentrantLock的含義,它能夠精確的指定哪些線程被叫醒,注意是哪些不是哪個(gè),我們來說一下Lock和Condition的本質(zhì)是什么,它的本質(zhì)是在synchronized里調(diào)用wait()和notify()的時(shí)候,它只有一個(gè)等待隊(duì)列,如果lock.newnewCondition()的時(shí)候,就變成了多個(gè)等待隊(duì)列,Condition的本質(zhì)就是等待隊(duì)列個(gè)數(shù),以前只有一個(gè)等待隊(duì)列,現(xiàn)在我new了兩個(gè)Condition,一個(gè)叫producer一個(gè)等待隊(duì)列出來了,另一個(gè)叫consumer第二個(gè)的等待隊(duì)列出來了,當(dāng)我們使用producer.await();的時(shí)候,指的是當(dāng)前線程進(jìn)入producer的等待隊(duì)列,使用producer.signalAll()指的是喚醒producer這個(gè)等待隊(duì)列的線程,consumner也是如此,所以上面的小程序就很容易理解了,我在生產(chǎn)者線程里叫醒consumer等待隊(duì)列的線程也就是消費(fèi)者線程,在消費(fèi)者線程里叫醒producer待隊(duì)列的線程也就是生產(chǎn)者線程,這個(gè)小程序的思路就是這樣了。

          源碼閱讀技巧和AQS源碼結(jié)構(gòu)解析

          我們會(huì)通過學(xué)習(xí)ReentrantLock來學(xué)習(xí)如何閱讀源碼,在之前的小程序,我們已經(jīng)了解過ReentrantLock了,就是創(chuàng)建ReentrantLock對(duì)象,調(diào)用對(duì)象的方法lock()和unlock()方法來加鎖解鎖,那么我們就來了解一下,在源碼中是如何實(shí)現(xiàn)這些的,首先,要承認(rèn)一點(diǎn),讀源碼很難,因?yàn)槟惚仨氁斫鈩e的思路,你要了解理解自己思路,或者理解跟你類似的人的思路,這件事是比較容易的,想法都是一樣的,都是住在農(nóng)村,從小在小河邊光著屁股長(zhǎng)大,我們聊河邊那些柳樹的問題,聊那些小蟲子的問題,他一聊我就能理解,可惜很不幸的是那邊是一白富美,人住在天宮,每天都用各種的口紅,用一些我們沒用過的,他一聊這些色號(hào)的時(shí)候,我就跟她聊不到一起去了,現(xiàn)在寫程序的這些人就像住在天宮的人,而你就是小河邊光屁股的小孩,你想理解的她的思路,你得先讓你的思維達(dá)到她那個(gè)高度再說,讀源碼不管你讀什么源碼,基本上第一個(gè)要有一定的數(shù)據(jù)結(jié)構(gòu)基礎(chǔ) ,第二個(gè)要有設(shè)計(jì)模式基礎(chǔ),其次是不要吹毛求疵,閱讀源碼貴在讀懂別人的思路就行,沒必要邊邊角角的都要去看,當(dāng)你達(dá)到手中無劍,心中有劍的境界的時(shí)候,你會(huì)發(fā)現(xiàn)這些東西都是互通的,概念是人類發(fā)明出來幫助大家理解問題的,如果死摳概念,這樣做是不對(duì)的,理解就好

          閱讀源碼的原則

          1、跑不起來的不讀

          跑不起來的源碼不要讀,看也看不懂,很難看懂,事倍功半,讀起來還費(fèi)勁,什么時(shí)候這個(gè)源碼必須得跑起來,跑起來有什么好處就是,你可以用debug一條線跟進(jìn)去,舉個(gè)例子,比如ReentrantLock的lock()方法,沒有跑起來的時(shí)候,靜態(tài)的來讀源碼你會(huì)怎么讀,按ctrl鼠標(biāo)單擊lock()方法,進(jìn)入這個(gè)方法,會(huì)看到這個(gè)方法調(diào)用了別的方法,你又會(huì)按ctrl鼠標(biāo)單擊進(jìn)入它調(diào)用的這個(gè)方法,一層層往下,你會(huì)發(fā)現(xiàn)沒法讀了,所以如果這個(gè)東西能跑起來就不一樣了,你會(huì)發(fā)現(xiàn)與之前鼠標(biāo)單擊跟進(jìn)的結(jié)果不一樣了,原因是因?yàn)槎鄳B(tài)的實(shí)現(xiàn),如果一個(gè)方法有很多子類的實(shí)現(xiàn),但是你不知道跟著這條線,它會(huì)去實(shí)現(xiàn)那個(gè)方法,所以你就得全部看一遍。

          2、解決問題就好 — 目的性

          在實(shí)際中解決問題就好,讀源碼一方面可以解決問題,一方面可以應(yīng)對(duì)面試,什么意思呢,如果你接手了一個(gè)別人改過6手的代碼,現(xiàn)在你的老板說這個(gè)代碼有些問題,你往里邊加一些功能或者修改一些bug,你解決問題就好,你不要從頭到尾去讀去改這個(gè)代碼,你讀你能累死你,目的性要強(qiáng),解決問題就好。

          3、一條線索到底

          讀源碼的時(shí)候要一條線索到底,不要只讀表面,我們知道一個(gè)程序跑起來以后,可能這個(gè)程序非常大,一個(gè)main方法有很多的put、get、size各種各樣其他的方法,每一個(gè)方法你調(diào)進(jìn)去,這個(gè)方法很有可能又去調(diào)別的方法,你不要每個(gè)方法先看一遍表面,然后再去里邊找,要一條線索到底,就讀一個(gè)方法,由淺到深看一遍。

          4、無關(guān)細(xì)節(jié)略過

          有那些邊界性的東西,在你讀第一遍的時(shí)候,沒必要的時(shí)候,你可以先把它略過。

          讀源碼的時(shí)候我建議大家自己去畫圖比如:


          這種圖叫甬道圖,這是UML圖的一種叫甬道,這個(gè)圖詮釋了哪個(gè)類里的哪個(gè)方法又去調(diào)用了哪個(gè)類里的的哪個(gè)方法,你讀源碼的時(shí)候,你一定要把它畫成圖給畫出來,其次是授人以魚不如授人以漁,把源碼從頭到尾帶著你那樣進(jìn)步不會(huì)太大 ,我把方法教給你,遠(yuǎn)比帶你要好的多。

          從上面的甬道圖可以看出,ReentrantLock調(diào)用它lock()方法的時(shí)候,它會(huì)調(diào)用acquire(1)方法,誰的acquire?是Sync的acquire,這很容易理解,在我們的lock方法里它調(diào)用了內(nèi)部的一個(gè)類,這個(gè)類叫NonfairSync,NonfairSync它里邊有個(gè)方法叫acquire(1),當(dāng)你讀到這里的時(shí)候,實(shí)際上你要開始分支了,你要知道這個(gè)Sync是個(gè)什么樣的類,大概掃一眼,前面說過要順著線讀進(jìn)去,然后你再看一下這個(gè)acquire(1)方法它內(nèi)部是怎么調(diào)用的,這個(gè)Sync是誰呢?它是一個(gè)NonfairSync,我們找到這個(gè)NonfairSync這個(gè)時(shí)候你再畫一張圖


          這個(gè)圖是說這個(gè)NonfairSync的父類是Sync,因?yàn)槟憧吹絅onfairSync子類里方法的時(shí)候,它有可能用到父類的方法,所以你要去父類里讀才可以,這個(gè)圖一定要畫好,NonfairSync的父類是Sync,好這個(gè)時(shí)候再回到Sync,這個(gè)時(shí)候我們看一下,這個(gè)Sync的父類又是誰?是AQS(
          AbstarctQueuedSynchronizer),這個(gè)時(shí)候說一下AQS是所有鎖的核心,我們繼續(xù)lock調(diào)用了

          acquire(1)這個(gè)方法,是誰的acquire(1)?是NonfairSync的acquire(1),NonfairSync又是誰?他是

          Sync的子類,Sync又是誰,Sync是AQS的子類,所以調(diào)用了acquire(1)我們?cè)俑M(jìn)去,這個(gè)時(shí)候就調(diào)用了AQS的acquire(1)了,AQS里面調(diào)用的是什么呢?是tryAcquire(1),再跟進(jìn)去你會(huì)發(fā)現(xiàn)這次調(diào)用的是NonfairSync里的tryAcquire(1),剛才我們讀的時(shí)候已經(jīng)知道AQS里有一個(gè)tryAcquire(1),但是它里面是拋出了一個(gè)異常,所以很容易理解,是NonfairSync里邊重寫了AQS里的tryAcquire(1),所以AQS里的acquire(1)調(diào)用了NonfairSync里的tryAcquire(1),我們?cè)賮砜碞onfairSync里的tryAcquire(1)又調(diào)用了nonfairTryAcquire(acquires),我們?cè)俑M(jìn)這個(gè)時(shí)候讀到這里,我們就必須要了解AQS了,如果不懂,就沒辦法繼續(xù)進(jìn)行下去了,我們看nonfairTryAcquire(acquires)方法實(shí)現(xiàn),開始它得到了當(dāng)前線程,跟線程有關(guān)系了,這個(gè)時(shí)候出現(xiàn)了一個(gè)方法getState(),我們來看一下這個(gè)方法,我們跟進(jìn)你會(huì)發(fā)現(xiàn),這個(gè)方法又進(jìn)到了AQS類里,這個(gè)getState()方法返回了一個(gè)state,這個(gè)state是什么呢?按著ctrl點(diǎn)過去,你會(huì)發(fā)現(xiàn)這個(gè)state就是一個(gè)volatile修飾的int類型的數(shù),這個(gè)時(shí)候就牽扯到AQS的結(jié)構(gòu)了。

          AQS源碼解析


          AQS隊(duì)列又可以稱為CLH隊(duì)列,AQS的核心是什么?

          就是這個(gè)state,這個(gè)state所代表的意思隨你定,隨子類來定,我們現(xiàn)在講的是ReentrantLock,剛才state的值是0,當(dāng)你獲得了之后它會(huì)變成1,就表示當(dāng)前線程得到了這把鎖,什么時(shí)候你釋放完了,state又會(huì)從1變回0,說明當(dāng)前線程釋放了這把鎖,所以這個(gè)state0和1就代表了加鎖和解鎖,所以這個(gè)state的值是根據(jù)你子類不同的實(shí)現(xiàn)取不同的意義,這個(gè)state的值的基礎(chǔ)之上,它的下面跟著一個(gè)隊(duì)列,這個(gè)隊(duì)列是AQS自己內(nèi)部所維護(hù)的隊(duì)列,這個(gè)隊(duì)列里邊每一個(gè)所維護(hù)的都是node一個(gè)節(jié)點(diǎn),它在哪里呢?

          它在AQS這個(gè)類里屬于AQS的內(nèi)部類,在這個(gè)node里最重要的一項(xiàng)是他里面保留了一個(gè)Thread一個(gè)線程,所以這個(gè)隊(duì)列是個(gè)線程隊(duì)列,而且還有兩個(gè)prev和next分別是前面的節(jié)點(diǎn)和后面的節(jié)點(diǎn),所以AQS里邊的隊(duì)列是這樣子的,一個(gè)一個(gè)的node,node里裝的是線程Thread,這個(gè)node它可以指向前面的這一個(gè),也可以指向后面的這一個(gè),所以叫雙向列表,所以AQS的核心是一個(gè)state,以及監(jiān)控這個(gè)state的雙向列表,每個(gè)列表里面有個(gè)節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)里邊裝的是線程,那么那個(gè)線程得到了state這把鎖,那個(gè)線程要等待,都要進(jìn)入這個(gè)隊(duì)列里邊,當(dāng)我們其中一個(gè)node得到了state這把鎖,就說明這個(gè)node里得線程持有這把鎖,所以當(dāng)我們acquire(1)上來以后看到這個(gè)state的值是0,那我就直接拿到state這個(gè)把鎖,現(xiàn)在是非公平上來就搶,搶不著就進(jìn)隊(duì)列里acquireQueued(),怎么是搶到呢?先得到當(dāng)前線程,然后獲取state的值,如果state的值等于0,用compareAndSetState(0,acquire)方法嘗試把state的值改為1,假如改成了setExclusiveOwnerThread()把當(dāng)前線程設(shè)置為獨(dú)占statie這個(gè)把鎖的狀態(tài),說明我已經(jīng)得到這個(gè)把鎖,而且這個(gè)把鎖是互斥的,我得到以后,別人是得不到的,因?yàn)閯e人再來的時(shí)候這個(gè)state的值已經(jīng)變成1了,如果說當(dāng)前線程已經(jīng)是獨(dú)占state這把鎖了,就往后加個(gè)1就表示可重入了。

          什么叫模板方法?

          模板方法是我在父類里有一個(gè)方法,就是這個(gè)tryAcquire(1),它調(diào)用了我子類的一些方法,這些子類的方法沒有實(shí)現(xiàn),我調(diào)用自己的方法事先寫好,但是由于這些方法就是給子類用來去重寫實(shí)現(xiàn)的,所以我就像一個(gè)模板一樣,我要打造一輛汽車,我要造地盤,造發(fā)動(dòng)機(jī),造車身,最后組裝,造地盤這件事子類去實(shí)現(xiàn),哪個(gè)車廠去實(shí)現(xiàn)是哪個(gè)車廠的事,奧迪是奧迪的事,奧托是奧托的事,車身也是發(fā)動(dòng)機(jī)也是,最后反正這個(gè)流程是一樣的,就像一個(gè)模板一樣,我的模板是固定的,里邊的方法是由具體的子類去實(shí)現(xiàn)的,當(dāng)我們開始造車的時(shí)候,就會(huì)調(diào)用具體子類去實(shí)現(xiàn)的函數(shù),所以叫為鉤子函數(shù),我勾著誰呢?勾著子類的實(shí)現(xiàn),這個(gè)就叫模板方法。

          什么叫非公平的去獲得(nonfairTryAcquire)?

          什么叫公平什么叫非公平,當(dāng)我們獲得一把鎖的時(shí)候,有一些等待隊(duì)列,如果說新來了一個(gè)線程要獲得這個(gè)鎖的時(shí)候,先去檢查等待隊(duì)列有沒有人,如果有后面排隊(duì),這個(gè)叫公平,上來二話不說,我才不管你的隊(duì)列有沒有人在等著,我上來就搶,搶著就算我的,插隊(duì)非公平。

          最后我們來總結(jié)一下閱讀源碼的原則,跑不起來的不讀、解決問題就好、一條線索到底、無關(guān)細(xì)節(jié)略過,在你閱讀的過程中,我希望你畫兩種圖,第一種方法之間的調(diào)用圖,哪個(gè)方法調(diào)用了哪個(gè)方法,第二種類之間的類圖,讀源碼重點(diǎn)在于你自己去讀,所以強(qiáng)烈建議,邊讀邊畫圖,這樣會(huì)更深刻。

          今天給大家分享的內(nèi)容是LockSupport、淘寶面試題與源碼閱讀方法論,
          明天給大家分享AQS源碼閱讀與強(qiáng)軟弱虛4種引用以及ThreadLocal原理與源碼的內(nèi)容。

          喜歡的朋友可以轉(zhuǎn)發(fā)關(guān)注一下~~~


          本文就是愿天堂沒有BUG給大家分享的內(nèi)容,大家有收獲的話可以分享下,想學(xué)習(xí)更多的話可以到微信公眾號(hào)里找我,我等你哦。

          瀏覽 35
          點(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>
                  np无码视频 | 国产精品视频免费 | 1313电影网 | 婷婷伦乱| AV噜噜噜 |