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

          你需要的并發(fā)編程總結(jié)

          共 22517字,需瀏覽 46分鐘

           ·

          2021-12-23 14:08

          線程和線程池

          線程

          線程和進(jìn)程的區(qū)別

          進(jìn)程是一個可執(zhí)行的程序,是系統(tǒng)分配資源的基本單位。線程是進(jìn)程內(nèi)部相對獨(dú)立的可執(zhí)行單元,是任務(wù)調(diào)度的基本單位。

          多線程的優(yōu)缺點(diǎn)

          優(yōu)點(diǎn):充分利用多核CPU的優(yōu)勢,提高CPU的利用率和程序運(yùn)行效率
          缺點(diǎn):1、線程過多影響性能,CPU切換增加內(nèi)存開銷。2、存在線程同步和線程安全問題。3、可能會發(fā)生死鎖。4、增加了開發(fā)人員的技術(shù)難度

          線程有幾種狀態(tài)

          5種狀態(tài):新建,就緒,運(yùn)行,阻塞和死亡。

          • 新建狀態(tài):new創(chuàng)建一個線程時(shí),還沒開始運(yùn)行,就是新建狀態(tài)。

          • 就緒狀態(tài):新建后,調(diào)用start()方法,線程就處于就緒態(tài),等待CPU調(diào)度。

          • 運(yùn)行狀態(tài):當(dāng)線程獲得了CPU時(shí)間后,進(jìn)入運(yùn)行狀態(tài),執(zhí)行run()里的內(nèi)容

          • 阻塞狀態(tài):線程運(yùn)行中隨時(shí)可能被阻塞:比如調(diào)用sleep()方法;等待獲取鎖被阻塞;線程在等待其他觸發(fā)條件。暫時(shí)讓出CPU資源。

          • 死亡狀態(tài):有兩個原因?qū)е戮€程死亡:run()方法正常結(jié)束;一個未捕獲的異常終止了run()方法

          一個線程OOM了,其他線程是否還能運(yùn)行

          答案是還能運(yùn)行,雖然堆是共享的,一個線程OOM了,可能說明其他線程也會OOM。但是當(dāng)一個線程OOM了,它所占據(jù)的空間會立刻釋放掉,從而不會影響其他線程的運(yùn)行。另外,如果主線程異常了,子線程也可以運(yùn)行。線程不像進(jìn)程,一個進(jìn)程之間的線程沒有父子之分,都是平級關(guān)系。

          創(chuàng)建線程的幾種方法

          • 繼承Thread類,重寫run()方法,利用Thread.start()啟動線程

          • 實(shí)現(xiàn)Runnable接口,重寫run()方法,利用new Thread(Runnable a)創(chuàng)建線程,調(diào)用start()方法啟動線程。

          • 通過Callable和futureTask創(chuàng)建線程,實(shí)現(xiàn)Callable接口,重寫call方法,利用future對象包裝callable實(shí)例,通過Thread方法創(chuàng)建線程。

          • 通過線程池創(chuàng)建線程

          sleep和wait

          • wait只能在synchronized中調(diào)用,屬于對象級別的方法,sleep不需要,屬于Thread的方法。

          • 調(diào)用wait方法會釋放鎖,sleep不會釋放鎖

          • wait超時(shí)之后線程進(jìn)入就緒狀態(tài),等待獲取cpu繼續(xù)執(zhí)行。

          yield和join

          • yield會釋放cpu資源,不會釋放鎖,讓當(dāng)前線程進(jìn)入就緒狀態(tài),只能使同優(yōu)先級或更高優(yōu)先級的線程有執(zhí)行的機(jī)會。

          • join會釋放cpu資源和鎖,底層是wait()方法實(shí)現(xiàn)的,join會等待調(diào)用join()方法的線程執(zhí)行完成之后再繼續(xù)執(zhí)行。

          死鎖

          死鎖定義

          死鎖是指兩個及以上的線程在執(zhí)行過程中,因爭奪資源而造成的一種互相等待(饑餓)的現(xiàn)象。若無外力作用,他們都將無法運(yùn)行下去

          死鎖原因

          • 系統(tǒng)資源的爭奪:系統(tǒng)中擁有不可剝奪的資源,其數(shù)量不足以滿足多個線程運(yùn)行的需要,使得線程在運(yùn)行過程因?yàn)闋帄Z資源而陷入僵局。

          • 線程推進(jìn)順序非法:線程在獲得一個鎖L1的情況下再去申請另一個鎖L2,也就是在沒有釋放鎖L1的情況下又去申請鎖L2,這個是產(chǎn)生死鎖最根本的原因。

          死鎖的必要條件

          • 互斥條件:進(jìn)程要求對所分配的資源在一段時(shí)間內(nèi)只能由一個進(jìn)程擁有。

          • 不可剝奪條件:資源在進(jìn)程未使用完成之前,不能被其他進(jìn)程奪走,除非是主動釋放

          • 請求和保持條件:進(jìn)程已經(jīng)保持了一個資源,又去申請另一個資源,但是該資源已經(jīng)被其他進(jìn)程占有。

          • 循環(huán)等待條件:進(jìn)程資源循環(huán)等待,A擁有資源1,申請資源2,B擁有資源2,申請資源1

          如何避免死鎖

          • 加鎖順序要合理

          • 加鎖時(shí)限要適當(dāng):線程嘗試獲取鎖要加上一定時(shí)限,超時(shí)就要放棄請求,同時(shí)釋放自己的鎖。

          • 死鎖檢測

          線程池

          概念

          就是個管理線程的池子。幫助我們管理線程,減少創(chuàng)建線程和銷毀線程的資源消耗。線程池的好處很多:提高響應(yīng)速度,直接從線程池中拿線程比創(chuàng)建線程快,而且線程可以重復(fù)利用。

          ThreadPoolExecutor

          線程池可以通過ThreadPoolExecutor創(chuàng)建,看下構(gòu)造函數(shù),總共7個參數(shù)

          public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,
          BlockingQueue workQueue,
          ThreadFactory threadFactory,
          RejectedExecutionHandler handler)
          復(fù)制代碼
          • corePoolSize:核心線程數(shù)

          • maximumPoolSize:最大線程數(shù)

          • keepAliveTime:線程池中非核心線程的空閑的存活時(shí)間

          • TimeUnit:keepAliveTime的時(shí)間單位

          • workQueue:存放任務(wù)的阻塞隊(duì)列

          • threadFactory:用于創(chuàng)建核心線程的線程工廠,可以給創(chuàng)建的線程定義名稱

          • handler:線程池的飽和策略(拒絕策略),有四種

          線程池的執(zhí)行流程

          四種拒絕策略

          • AbortPolicy:直接拒絕,拋出一個異常,默認(rèn)的拒絕策略

          • DiscardPolicy:直接丟棄任務(wù)

          • DiscardOldestPolicy:丟棄任務(wù)里最老的任務(wù),將當(dāng)前這個任務(wù)繼續(xù)提交給線程池

          • CallerRunsPolicy:交給線程池調(diào)用所在的線程進(jìn)行處理

          阻塞隊(duì)列

          • ArrayBlockingQueue:有界隊(duì)列,是一個數(shù)組實(shí)現(xiàn)的有界阻塞隊(duì)列,按照FIFO排序。

          • LindkedBlockingQueue:基于鏈表實(shí)現(xiàn)的阻塞隊(duì)列,按照FIFO排序,容量可以設(shè)置,不設(shè)置的話就是一個無邊界的阻塞隊(duì)列(最大長度是Integer.MAX_VALUE),吞吐量要高于ArrayBlockingQueue。newFixedThreadPool就是使用的這個隊(duì)列。

          • DelayQueue:一個任務(wù)定時(shí)周期延遲執(zhí)行的隊(duì)列,根據(jù)指定的執(zhí)行從小到大排序,否則根據(jù)插入到隊(duì)列的先后順序,newScheduledThreadPool使用的這個隊(duì)列。

          • PriorityBlockingQueue:優(yōu)先級隊(duì)列是具有優(yōu)先級的無界阻塞隊(duì)列。

          • SynchronousQueue:同步隊(duì)列,一個不存儲元素的阻塞隊(duì)列,每個插入操作必須等待另一個線程調(diào)用移除操作,否則插入操作一直阻塞。newCachedThreadPool使用了這個隊(duì)列。

          常用線程池

          newFixedThreadPool

          固定線程數(shù)目的線程池,內(nèi)部使用LinkedBlockingQueue

          public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
          return new ThreadPoolExecutor(nThreads, nThreads,
          0L, TimeUnit.MILLISECONDS,
          new LinkedBlockingQueue(),
          threadFactory);
          }
          復(fù)制代碼
          • 核心線程數(shù)=最大線程數(shù)

          • 沒有非空閑時(shí)間,即keepAliveTime=0

          • 阻塞隊(duì)列是無界隊(duì)列LinkedBlockingQueue。

          • 使用場景:適用于處理CPU密集型的任務(wù),確保CPU在長期工作線程使用的情況下,盡可能少的分配線程。

          newCachedThreadPool

          可緩存線程的線程池,內(nèi)部使用SynchronousBlockingQueue

          public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
          return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
          60L, TimeUnit.SECONDS,
          new SynchronousQueue(),
          threadFactory);
          }
          復(fù)制代碼
          • 核心線程數(shù)=0,最大線程數(shù)=Integer.Max_VALUE

          • 非核心線程存活時(shí)間為60s

          • 這個池也有一個問題:當(dāng)提交的任務(wù)數(shù)量大于處理任務(wù)的數(shù)量時(shí),每次提交一個任務(wù)必然會創(chuàng)建一個非核心線程,極端情況下會創(chuàng)建過多的線程,耗盡CPU和內(nèi)存

          • 使用場景:用于并發(fā)量大執(zhí)行大量短期的小任務(wù)

          newSingleThreadPool

          單線程的線程池,內(nèi)部使用linkedBlockingQueue

          public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
          return new FinalizableDelegatedExecutorService
          (new ThreadPoolExecutor(1, 1,
          0L, TimeUnit.MILLISECONDS,
          new LinkedBlockingQueue(),
          threadFactory));
          }
          復(fù)制代碼
          • 核心線程數(shù)=最大線程數(shù)=1,也就是這個池子里始終都只有一個活著的線程。

          • keepAliveTime=0,這個參數(shù)無效

          • 阻塞隊(duì)列是無界的LinkedBlockingQueue

          • 使用場景:適用于串行執(zhí)行任務(wù)的場景,一個任務(wù)接一個任務(wù)執(zhí)行。

          newScheduledThreadPool

          定時(shí)及周期性執(zhí)行的線程池,內(nèi)部使用DelayQueue

          public ScheduledThreadPoolExecutor(int corePoolSize) {
          super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
          }
          復(fù)制代碼
          • 核心線程自定義,最大線程數(shù)為Integer.Max_Value

          • keepAliveTime=0

          • 使用場景:周期性執(zhí)行任務(wù)的場景。

          這里scheduleAtFixedRate()和scheduleWithFixedDelay()兩個方法區(qū)別如下:

          • scheduleAtFixedRate:按某種速率周期性執(zhí)行,不管上一個任務(wù)是否結(jié)束。

          • scheduleWithFixedDelay:在某個延遲之后執(zhí)行,是要等上一個任務(wù)執(zhí)行結(jié)束開始算起的。

          線程池狀態(tài)

          5個狀態(tài):RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED

          //線程池狀態(tài)
          private static final int RUNNING = -1 << COUNT_BITS;
          private static final int SHUTDOWN = 0 << COUNT_BITS;
          private static final int STOP = 1 << COUNT_BITS;
          private static final int TIDYING = 2 << COUNT_BITS;
          private static final int TERMINATED = 3 << COUNT_BITS;
          復(fù)制代碼

          • Running:該狀態(tài)的線程池會接收新任務(wù),并處理阻塞隊(duì)列中的任務(wù)。調(diào)用shudown()方法可切換到SHUTDOWN狀態(tài)。調(diào)用shutdownNow()方法變?yōu)镾TOP狀態(tài)。

          • Shutdown:該狀態(tài)線程池不會接收新任務(wù),但會處理阻塞隊(duì)列中的任務(wù)。隊(duì)列為空,并且線程池中執(zhí)行的任務(wù)也為空,進(jìn)入TIDYING狀態(tài)。

          • Stop:該狀態(tài)的線程池不會接收新任務(wù),也不會處理隊(duì)列中的任務(wù),而且會中斷正在執(zhí)行的任務(wù)。

          • Tidying:該狀態(tài)表明所有任務(wù)運(yùn)行終止,記錄的任務(wù)數(shù)量為0.terminated()執(zhí)行完畢進(jìn)入TERMINATED狀態(tài)。

          • Terminated:該狀態(tài)表明線程池終止或死亡。

          ThreadLocal

          ThreadLocal是什么

          表面看ThreadLocal是和多線程和線程同步有關(guān)的一個工具類,但它與線程同步機(jī)制無關(guān)。線程同步機(jī)制是多個線程共享一個變量,而ThreadLocal是為每個線程創(chuàng)建一個單獨(dú)的變量副本,每個線程都可以改變自己的變量副本而不影響其他線程對應(yīng)的副本。
          官方API:該類提供了線程局部變量。這些變量不同于他們的普通對應(yīng)物,因?yàn)樵L問某個變量的每個線程都有自己的局部變量,他獨(dú)立于變量的初始化副本。ThreadLocal實(shí)例通常是private static字段,他們希望將狀態(tài)與某一個線程(例如用戶ID或事物ID)相關(guān)聯(lián)。

          API

          四個方法

          • get():返回此線程局部變量當(dāng)前副本的值

          • set(T value):將線程局部變量當(dāng)前副本的值設(shè)置為指定值。

          • initialValue():返回此線程局部變量當(dāng)前副本的初始值。

          • remove():移除此線程局部變量當(dāng)前副本的值

          ThreadLocal有一個重要的靜態(tài)內(nèi)部類ThreadLocalMap,該類是實(shí)現(xiàn)線程隔離機(jī)制的關(guān)鍵。get(),set(),remove()等都是基于該map操作,ThreadLocalMap的key為當(dāng)前ThreadLocal對象,value為對應(yīng)線程的變量副本,每個線程都有自己的ThreadLocal對象,也就是都有自己的ThreadLocalMap,對自己的ThreadLocalMap操作當(dāng)然是互不影響的,這就不存在線程安全問題了。

          使用示例

          假設(shè)每個線程都需要計(jì)數(shù),且在運(yùn)行時(shí)都要修改計(jì)數(shù)器的值,那么ThreadLocal就是很好的選擇了。

          public class SeqCount {

          private static ThreadLocal threadLocal = new ThreadLocal() {

          @Override
          protected Integer initialValue() {
          return 0;
          }
          };

          public int nextSeq() {
          threadLocal.set(threadLocal.get()+1);
          return threadLocal.get();
          }

          public static void main(String [] args) {
          SeqCount seqCount = new SeqCount();

          SeqThread seqThread1 = new SeqThread(seqCount);
          SeqThread seqThread2 = new SeqThread(seqCount);
          SeqThread seqThread3 = new SeqThread(seqCount);

          Thread thread1 = new Thread(seqThread1);
          Thread thread2 = new Thread(seqThread2);
          Thread thread3 = new Thread(seqThread3);

          thread1.start();
          thread2.start();
          thread3.start();
          }

          public static class SeqThread implements Runnable {

          private SeqCount seqCount;

          public SeqThread(SeqCount seqCount) {
          this.seqCount = seqCount;
          }

          @Override
          public void run() {
          for (int i=0; i<5; i++) {
          System.out.println(Thread.currentThread().getName()+",seqCount="+seqCount.nextSeq());
          }
          }
          }
          }
          /*
          一個可能的輸出結(jié)果
          Thread-0,seqCount=1
          Thread-0,seqCount=2
          Thread-0,seqCount=3
          Thread-0,seqCount=4
          Thread-0,seqCount=5
          Thread-1,seqCount=1
          Thread-1,seqCount=2
          Thread-1,seqCount=3
          Thread-1,seqCount=4
          Thread-1,seqCount=5
          Thread-2,seqCount=1
          Thread-2,seqCount=2
          Thread-2,seqCount=3
          Thread-2,seqCount=4
          Thread-2,seqCount=5
          */
          復(fù)制代碼

          內(nèi)存泄露問題

          ThreadLocalMap的key為ThreadLocal實(shí)例,它是一個弱引用,我們知道弱引用有利于GC回收,當(dāng)key==null時(shí),GC就會回收這部分空間,但value不一定能被回收,因?yàn)樗虲urrent Thread之間還存在一個強(qiáng)引用的關(guān)系,由于這個強(qiáng)引用的關(guān)系會導(dǎo)致value無法被回收。如果線程對象不消除這個強(qiáng)引用關(guān)系,就可能出現(xiàn)OOM。

          Java內(nèi)存模型(JMM)

          硬件內(nèi)存結(jié)構(gòu)

          處理器和計(jì)算機(jī)存儲設(shè)備之間運(yùn)算速度相差幾個數(shù)量級,為了達(dá)到“高并發(fā)”的效果,在處理器和內(nèi)存之間加了高速緩存“Cache”。

          將運(yùn)算需要使用的數(shù)據(jù)復(fù)制到緩存中,讓運(yùn)算能夠快速進(jìn)行,當(dāng)運(yùn)算完成后,再將緩存中的結(jié)果寫入主內(nèi)存中,這樣運(yùn)算器就不用等待主內(nèi)存的讀寫操作。每個處理器都有自己的高速緩存,同時(shí)又共同操作同一塊主存,當(dāng)多個處理器同時(shí)操作主存時(shí),可能導(dǎo)致數(shù)據(jù)不一致,因此需要“緩存一致性協(xié)議”保障。

          Java內(nèi)存模型

          Java內(nèi)存模型簡稱JMM(Java Memory Model),用來屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,實(shí)現(xiàn)讓Java程序在各平臺下都能夠達(dá)到一致的內(nèi)存訪問效果。JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲在主內(nèi)存,每個線程都有一個私有的本地內(nèi)存,本地內(nèi)存存儲了該線程讀寫變量的副本。本地內(nèi)存是JMM的一個抽象概念,不是真實(shí)存在。

          主內(nèi)存:主要存儲Java實(shí)例對象,所有線程創(chuàng)建的實(shí)例對象都存放在主存中,不管該實(shí)例對象是成員變量還是方法中的局部變量,當(dāng)然也包括共享的類信息、常量、靜態(tài)變量。共享數(shù)據(jù)區(qū)域,多個線程對同一個變量訪問可能會出現(xiàn)線程安全問題。
          工作內(nèi)存:主要存儲當(dāng)前方法的所有本地變量信息,每個線程只能訪問自己的工作內(nèi)存,即線程中的本地變量對其他線程是不可見的,由于工作內(nèi)存是每個線程的私有數(shù)據(jù),線程間無法相互訪問工作內(nèi)存,因此存儲在工作內(nèi)存的數(shù)據(jù)不存在線程安全問題,例如ThreadLocal。

          JMM的8種內(nèi)存交互

          內(nèi)存交互有8種,虛擬機(jī)必須保證每個操作都是原子的,不可再分的

          • lock(鎖定):作用于主內(nèi)存的變量,把一個變量標(biāo)識為線程獨(dú)占狀態(tài)。

          • read(讀取):作用于主內(nèi)存變量,把一個變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存,以便隨后的load動作使用。

          • load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中的得到的變量放入工作內(nèi)存的變量副本中。

          • use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存的變量傳輸?shù)綀?zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個需要使用到變量的值的字節(jié)碼指令時(shí)將會執(zhí)行這個操作。

          • assign(賦值):作用工作內(nèi)存的變量,它把一個從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量副本。

          • store(存儲):作用于工作內(nèi)存的變量,它把一個從工作內(nèi)存的一個變量的值傳送到主內(nèi)存中,以便后續(xù)write使用。

          • write(寫入):作用主內(nèi)存的變量,它把store操作從工作內(nèi)存得到的變量的值放入主內(nèi)存的變量中。

          • unlock(解鎖):作用于主內(nèi)存的變量,它把一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定。

          如果是將變量從主內(nèi)存復(fù)制到工作內(nèi)存,就是read->load->use,如果是將變量從工作內(nèi)存同步到主內(nèi)存,就是assign->store->write,JMM要求按順序執(zhí)行,但不是必須連續(xù)執(zhí)行,中間可以插入其他操作。

          JMM的三個特性

          原子性

          JMM提供了read、load、use、assign、store和write六個指令直接提供原子操作,我們可以認(rèn)為java的基本變量的讀寫操作是原子的(除了double和long,因?yàn)橛行┨摂M機(jī)可以將64位分為高32位和低32位分開運(yùn)算)。對于lock、unlock,虛擬機(jī)沒有將操作直接給用戶使用,而是提供了更高層次的字節(jié)碼指令monitorenter和monitorexit來隱式使用這兩個操作,對應(yīng)java中的synchronized關(guān)鍵字。

          可見性

          線程之間是隔離的,線程拿到的是主存變量的副本,更改變量需要刷新回主存,其他線程需要從主存重新獲取才能拿到變更的值。所有變量都需要經(jīng)過這個過程,但是volatile修飾的變量可以在修改后強(qiáng)制刷回到主存,并在使用時(shí)從主存獲取。
          synchronized在執(zhí)行完畢之后,進(jìn)行Unlock之前,必須將共享變量同步回主內(nèi)存中(執(zhí)行store和write操作)
          final修飾的字段,只要在構(gòu)造函數(shù)中一旦初始化完成,且沒有對象逃逸,那么在其他線程中就可以看到final字段的值。

          有序性

          有序性:在單線程觀察到的結(jié)果是有序的。但在多線程下,就會出現(xiàn)亂序的情況。
          Java提供了volatile和synchronized來保證線程的有序性。volatile使用內(nèi)存屏障,而synchronized基于lock之后必須unlock,其他線程才能重新lock的規(guī)則,讓同步塊串行執(zhí)行。

          Happens-Before

          先行發(fā)生是java內(nèi)存模型中定義的兩個操作的順序,如果說操作A先行發(fā)生于操作B,操作A產(chǎn)生的影響能被B觀察到,“影響”包括修改了內(nèi)存中共享變量的值,發(fā)送了消息,調(diào)用了方法等。
          在JMM下具有一些天然的先行發(fā)生關(guān)系:

          • 程序次序原則:在一個線程內(nèi),程序的執(zhí)行規(guī)則跟程序的書寫規(guī)則是一致的,從上往下執(zhí)行。

          • 鎖定規(guī)則:一個Unlock操作肯定先于下一次Lock操作。這里必須是同一個鎖。

          • volatile變量規(guī)則:對同一個volatile變量,先行發(fā)生的寫操作,肯定早于后續(xù)發(fā)生的讀操作。

          • 線程啟動規(guī)則:Thread對象的start()操作先行發(fā)生于此線程的每一個動作。

          • 線程終止規(guī)則:Thread對象的終止檢測操作肯定晚于線程中的所有操作。

          • 對象終止規(guī)則:一個對象的初始化方法肯定早于它的finalize()方法

          • 傳遞性:如果操作A先于操作B,操作B先于操作C,則操作A先于操作C。

          CAS

          CAS定義

          CAS(Compare And Swap),比較并交換。Doug Lea大神在同步組件中大量使用了CAS技術(shù)實(shí)現(xiàn)了Java多線程的并發(fā)操作。整個AQS組件、Atomic原子類操作都是基于CAS實(shí)現(xiàn)的,甚至ConcurrentHashMap在JDK1.8中也將ReentrantLock改為CAS+Synchronized。

          CAS原理

          在CAS中有三個參數(shù):內(nèi)存值V、舊的預(yù)期值A(chǔ)、要更新的值B,當(dāng)且僅當(dāng)內(nèi)存值V的值等于舊的預(yù)期值A(chǔ)時(shí)才會將內(nèi)存值V的值修改為B,否則什么也不干??梢韵胂鬄槭裁??當(dāng)一個線程向更新這個變量的值,內(nèi)存值是V,如果沒有其他線程修改這個變量,那么理所當(dāng)然認(rèn)為它的預(yù)期值A(chǔ)=V,這個時(shí)候更新才不會發(fā)生問題。偽代碼如下:

          if (this.value==A){
          this.value=B;
          return true;
          } else{
          return false;
          }
          復(fù)制代碼

          下面以AtomicInteger為例闡述CAS的實(shí)現(xiàn)

          private static final Unsafe unsafe = Unsafe.getUnsafe();
          private static final long valueOffset;

          static {
          try {
          valueOffset = unsafe.objectFieldOffset
          (AtomicInteger.class.getDeclaredField("value"));
          } catch (Exception ex) { throw new Error(ex); }
          }

          private volatile int value;
          復(fù)制代碼

          CAS可以保證一次讀寫操作是原子操作,在單處理器上容易實(shí)現(xiàn),但是在多處理器上實(shí)現(xiàn)有點(diǎn)復(fù)雜,CPU提供了兩種方法實(shí)現(xiàn)多處理器的原子操作:

          • 總線加鎖:總線加鎖就是使用處理器提供一個LOCK#信號,當(dāng)一個處理器在總線上輸出此信號時(shí),其他處理器的請求將被阻塞住,那么該處理器可以獨(dú)占使用共享內(nèi)存。這種處理方式一刀切,在鎖定期間,其他處理器都不能訪問內(nèi)存地址的數(shù)據(jù),開銷大。

          • 緩存加鎖:針對上面的情況我們只需要保證在同一時(shí)刻對某個內(nèi)存地址的操作是原子性即可。緩存加鎖就是緩存在內(nèi)存區(qū)域的數(shù)據(jù)如果在加鎖期間,當(dāng)它執(zhí)行鎖操作寫回內(nèi)存時(shí),處理器不再輸出LOCK#信號,而是修改內(nèi)部的內(nèi)存地址,利用緩存一致性協(xié)議保證原子性。緩存一致性機(jī)制可以保證同一個內(nèi)存區(qū)域的數(shù)據(jù)僅能被一個處理器修改,也就是說當(dāng)CPU1修改緩存行中的i時(shí)使用緩存鎖定,那么CPU2就不能同時(shí)緩存了i的緩存行。

          CAS的缺陷

          循環(huán)時(shí)間太長

          如果自旋CAS長時(shí)間不成功,則會給CPU帶來非常大的開銷,在JUC有些地方就限制了CAS自旋的次數(shù)。

          只能保證一個共享變量原子操作

          CAS只針對一個共享變量,如果是多個共享變量就只能使用鎖了。

          ABA

          CAS需要檢查操作值有沒有改變,如果沒有改變就更新,但是存在一種情況,一個變量原來值=A,后來變成了B,然后又變成了A,那么在CAS檢查的時(shí)候會發(fā)現(xiàn)沒改變。這就是所謂的ABA問題。對于這種問題的解決方法就是加上版本號,即在每個變量加上版本號,每次改變都加1,則A->B->A變成1A->2B->3A。

          AQS

          AQS介紹

          AQS(AbstractQueuedSynchronizer)核心思想是:如果被請求的共享資源空閑,則將當(dāng)前請求資源的線程設(shè)置為有效的工作線程,并且將共享資源設(shè)置為鎖定狀態(tài)。如果被請求的共享資源被占用,那么就需要一套線程阻塞等待以及被喚醒時(shí)所分配的機(jī)制,這個機(jī)制AQS是用CLH隊(duì)列鎖實(shí)現(xiàn)的,即把暫時(shí)獲取不到鎖的線程加入到隊(duì)列中。
          CLH是一個虛擬機(jī)的雙向隊(duì)列(虛擬的雙向隊(duì)列不存在隊(duì)列實(shí)例,僅存在結(jié)點(diǎn)之間的關(guān)聯(lián)關(guān)系)。AQS是將每條請求共享資源的線程封裝成一個CLH鎖隊(duì)列的一個結(jié)點(diǎn)Node來實(shí)現(xiàn)鎖的分配。

          隊(duì)列同步器AQS(以下簡稱同步器)是用來構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架,它使用一個int類型的成員變量state表示同步狀態(tài),通過內(nèi)置的FIFO隊(duì)列來完成資源獲取線程的排隊(duì)工作。
          同步器的主要使用方式是繼承,子類繼承同步器并實(shí)現(xiàn)它的抽象方法來管理同步狀態(tài),在抽象方法的實(shí)現(xiàn)過程中免不了要對同步狀態(tài)進(jìn)行更改,這時(shí)就需要使用同步器提供的三個方法getState()、setState(int newState)和campareAndSeState(int expect, int update)來進(jìn)行操作。
          同步器既可以支持獨(dú)占式獲取同步狀態(tài)、也支持共享式,這樣就可以實(shí)現(xiàn)不同類型的同步組件(ReentrantLock、ReenTrantReadWriteLock以及CountdownLock等)。
          同步器和鎖的關(guān)系:同步器是實(shí)現(xiàn)鎖的關(guān)鍵,在鎖的實(shí)現(xiàn)中聚合同步器,利用同步器實(shí)現(xiàn)鎖的語義。鎖是面向使用者,定義好了接口,屏蔽了實(shí)現(xiàn)細(xì)節(jié)。同步器面向的是鎖的實(shí)現(xiàn)者,它簡化了鎖的實(shí)現(xiàn)方式。
          同步器的設(shè)計(jì)是基于模板方法模式,也就是說使用者需要繼承同步器并重寫指定的方法,隨后將同步器組合在自定義同步組件的實(shí)現(xiàn)中,并調(diào)用同步器提供的模板方法,而這些模板方法將會調(diào)用重寫的方法
          同步器可重寫的方法:

          方法名稱描述
          protected boolean tryAcquire(int arg)獨(dú)占式獲取同步狀態(tài),實(shí)現(xiàn)該方法需要查詢當(dāng)前狀態(tài)并判斷同步狀態(tài)是否符合預(yù)期,然后再進(jìn)行CAS設(shè)置同步狀態(tài)
          protected boolean tryRelease(int arg)獨(dú)占式釋放同步狀態(tài),等待獲取同步狀態(tài)的線程將有機(jī)會獲取同步狀態(tài)
          protected int tryAcquireShared(int arg)共享式獲取同步狀態(tài),返回大于等于0的值表示獲取成功,否則獲取失敗
          protected boolean tryReleaseShared(int arg)共享式釋放同步狀態(tài)
          protected boolean isHeldExclusively()當(dāng)前同步器是否在獨(dú)占模式下被線程占用,一般該方法表示是否被當(dāng)前線程所獨(dú)占
          同步器提供的模板方法分為三類:獨(dú)占式獲取與釋放同步狀態(tài)、共享式獲取與釋放同步狀態(tài)、查詢同步隊(duì)列中的等待線程情況。自定義同步組件將使用同步器提供的模板方法實(shí)現(xiàn)自己的同步語義。

          Mutex獨(dú)占鎖的實(shí)現(xiàn)

          獨(dú)占鎖就是在同一時(shí)刻只能有一個線程獲取到鎖,其他未獲取到鎖的線程只能在同步隊(duì)列中等待,只有等獲取鎖的線程釋放了鎖,后續(xù)線程才能繼續(xù)獲取鎖。

          public class Mutex implements Lock{

          private static class Sync extends AbstractQueuedSynchronizer {
          /**
          * 是否處于占用狀態(tài)
          * @return
          */
          @Override
          protected boolean isHeldExclusively() {
          return getState() == 1;
          }

          /**
          * 當(dāng)狀態(tài)為0時(shí)獲取鎖
          * @param acquires
          * @return
          */
          @Override
          protected boolean tryAcquire(int acquires) {
          assert acquires == 1;
          if (compareAndSetState(0,1)) {
          setExclusiveOwnerThread(Thread.currentThread());
          return true;
          }
          return false;
          }

          /**
          * 釋放鎖,將狀態(tài)設(shè)置為0
          * @param acquires
          * @return
          */
          @Override
          protected boolean tryRelease(int acquires) {
          assert acquires == 1;
          if (getState() == 0) {
          throw new IllegalMonitorStateException();
          }
          setExclusiveOwnerThread(null);
          setState(0);
          return true;
          }

          /**
          * 返回一個Condition,每個COndition都包含了一個condition隊(duì)列
          * @return
          */
          Condition newConditon() {
          return new ConditionObject();
          }
          }

          private final Sync sync = new Sync();

          @Override
          public void lock() {
          sync.acquire(1);
          }

          @Override
          public boolean tryLock() {
          return sync.tryAcquire(1);
          }

          @Override
          public void unlock() {
          sync.release(1);
          }

          @Override
          public Condition newCondition() {
          return sync.newConditon();
          }

          public boolean isLocked() {
          return sync.isHeldExclusively();
          }

          public boolean hasQueuedThreads() {
          return sync.hasQueuedThreads();
          }

          @Override
          public void lockInterruptibly() throws InterruptedException{
          sync.acquireInterruptibly(1);
          }

          @Override
          public boolean tryLock(long time, TimeUnit unit) throws InterruptedException{
          return sync.tryAcquireNanos(1, unit.toNanos(time));
          }
          }
          復(fù)制代碼

          上面的代碼中,獨(dú)占鎖Mutex是一個自定義同步組件,它定義了一個靜態(tài)內(nèi)部類,該內(nèi)部類Sync繼承了同步器并實(shí)現(xiàn)了獨(dú)占式獲取和釋放同步狀態(tài)。在tryAcquire(int acquires)方法中,如果經(jīng)過CAS設(shè)置成功,則表示獲取到了同步狀態(tài)。在tryRelease(int releases)方法中只是將同步狀態(tài)置為0。

          源碼解讀

          同步隊(duì)列

          同步器依賴內(nèi)部的同步隊(duì)列來完成同步狀態(tài)的管理,當(dāng)前線程獲取同步狀態(tài)失敗時(shí),同步器會將當(dāng)前線程以及等待狀態(tài)信息構(gòu)造成一個Node結(jié)點(diǎn)放入同步隊(duì)列,同時(shí)會阻塞當(dāng)前線程,當(dāng)同步狀態(tài)釋放時(shí),會把頭結(jié)點(diǎn)中的線程喚醒,使其再次嘗試獲取同步狀態(tài)。
          同步隊(duì)列中的Node結(jié)點(diǎn)保存了線程引用、等待狀態(tài)以及前驅(qū)和后繼結(jié)點(diǎn),結(jié)點(diǎn)的屬性類型與名稱如下:

                  //因?yàn)槌瑫r(shí)或者中斷,節(jié)點(diǎn)會被設(shè)置成取消狀態(tài),被取消的節(jié)點(diǎn)不會參與到競爭中,保持取消狀態(tài)不會改變
          static final int CANCELLED = 1;
          //后繼節(jié)點(diǎn)的線程處于等待狀態(tài),而當(dāng)前節(jié)點(diǎn)的線程如果釋放了同步狀態(tài)或者被取消了,將會通知后繼節(jié)點(diǎn),使后繼節(jié)點(diǎn)的線程得以運(yùn)行
          static final int SIGNAL = -1;
          //表示節(jié)點(diǎn)在等待隊(duì)列中,節(jié)點(diǎn)線程等待在Condition上,當(dāng)其他線程對Condition調(diào)用了signal方法后,該節(jié)點(diǎn)將會從等待隊(duì)列中轉(zhuǎn)移到同步隊(duì)列中,加入到對同步狀態(tài)的獲取中
          static final int CONDITION = -2;
          //表示下一次共享式同步狀態(tài)獲取將會無條件被傳播下去
          static final int PROPAGATE = -3;
          復(fù)制代碼

          Node中還定義了不同的節(jié)點(diǎn)

          //后繼節(jié)點(diǎn)
          volatile Node next;
          //前驅(qū)節(jié)點(diǎn),當(dāng)節(jié)點(diǎn)加入同步隊(duì)列時(shí)被設(shè)置
          volatile Node prev;
          //獲取同步狀態(tài)的線程
          volatile Thread thread;
          //等待隊(duì)列中的后繼節(jié)點(diǎn),如果當(dāng)前節(jié)點(diǎn)是共享的,那么該字段是一個SHARED常量,也就是說節(jié)點(diǎn)類型(獨(dú)占和共享)和等待隊(duì)列中的后繼節(jié)點(diǎn)共有一個字段。
          Node nextWaiter;
          復(fù)制代碼

          節(jié)點(diǎn)是構(gòu)成同步隊(duì)列的基礎(chǔ),同步器擁有頭結(jié)點(diǎn)和尾結(jié)點(diǎn),沒有成功獲得同步狀態(tài)的線程會加入隊(duì)列尾部

          同步器遵循FIFO,頭結(jié)點(diǎn)是獲取同步狀態(tài)成功的節(jié)點(diǎn),頭結(jié)點(diǎn)的線程在釋放同步狀態(tài)時(shí),將會喚醒后繼節(jié)點(diǎn),而后繼節(jié)點(diǎn)在獲取同步狀態(tài)成功時(shí)會將自己設(shè)置成頭結(jié)點(diǎn)。

          獨(dú)占式同步狀態(tài)的獲取與釋放

          acquire方法

          調(diào)用同步器的acquire(int arg)方法可以獲取同步狀態(tài),該方法對中斷不敏感

          public final void acquire(int arg) {
          //嘗試獲取鎖
          if (!tryAcquire(arg) &&
          //獲取不到,則進(jìn)入等待隊(duì)列,返回是否中斷
          //acquireQueued返回true表示中斷
          acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
          //如果返回中斷,則調(diào)用當(dāng)前線程的interrupt方法
          selfInterrupt();
          }
          復(fù)制代碼

          首先調(diào)用自定義同步器實(shí)現(xiàn)的tryAcquire()方法,如果失敗則構(gòu)造節(jié)點(diǎn),并通過addWaiter()將該節(jié)點(diǎn)加入同步隊(duì)列的尾部,最后調(diào)用acquireQueued(Node node, int arg)方法,使得該節(jié)點(diǎn)以自旋方式獲取同步狀態(tài),如果獲取不到則調(diào)用LockSupport.park(this)阻塞節(jié)點(diǎn)中的線程。

          private Node addWaiter(Node mode) {
          //將當(dāng)前線程封裝成Node,并且mode為獨(dú)占鎖
          Node node = new Node(Thread.currentThread(), mode);
          //tail是AQS中同步隊(duì)列的隊(duì)尾
          Node pred = tail;
          if (pred != null) {
          //將當(dāng)前節(jié)點(diǎn)node的前驅(qū)節(jié)點(diǎn)設(shè)置為尾結(jié)點(diǎn)
          node.prev = pred;
          //CAS設(shè)置尾結(jié)點(diǎn)
          if (compareAndSetTail(pred, node)) {
          pred.next = node;
          return node;
          }
          }
          //第一次添加tail=null,將node添加到同步隊(duì)列中
          enq(node);
          return node;
          }
          復(fù)制代碼
          private Node enq(final Node node) {
          //循環(huán)+CAS
          for (;;) {
          Node t = tail;
          //如果是第一次添加到隊(duì)列,那么tail=null
          if (t == null) { // Must initialize
          //CAS設(shè)置頭結(jié)點(diǎn)
          if (compareAndSetHead(new Node()))
          tail = head;
          }
          //否則添加邏輯和addWaiter相似
          else {
          node.prev = t;
          if (compareAndSetTail(t, node)) {
          t.next = node;
          return t;
          }
          }
          }
          }
          復(fù)制代碼

          在enq(final Node node)中,同步器通過死循環(huán)保證節(jié)點(diǎn)的正確添加,在死循環(huán)中只有通過CAS將節(jié)點(diǎn)設(shè)置成尾結(jié)點(diǎn)后,當(dāng)前線程才返回。節(jié)點(diǎn)進(jìn)入同步隊(duì)列后就進(jìn)入自旋過程,每個節(jié)點(diǎn)(或線程)都在觀察自己的條件是否滿足,一旦獲取到同步狀態(tài)就從自旋過程中退出。

          final boolean acquireQueued(final Node node, int arg) {
          boolean failed = true;
          try {
          boolean interrupted = false;
          for (;;) {
          final Node p = node.predecessor();
          //其前驅(qū)是頭結(jié)點(diǎn),并且再次調(diào)用tryAcquire成功獲得鎖
          if (p == head && tryAcquire(arg)) {
          //將自己設(shè)為頭結(jié)點(diǎn)
          setHead(node);
          p.next = null; // help GC
          failed = false;
          //成功獲得鎖,返回
          return interrupted;
          }
          //沒有得到鎖時(shí),shouldParkAfterFailedAcquire方法:返回是否需要阻塞當(dāng)前線程
          //parkAndCheckInterrupt方法:阻塞當(dāng)前線程,當(dāng)線程再次喚醒時(shí),返回是否中斷
          if (shouldParkAfterFailedAcquire(p, node) &&
          parkAndCheckInterrupt())
          //修改中斷標(biāo)志位
          interrupted = true;
          }
          } finally {
          if (failed)
          //獲取鎖失敗,則將此線程對應(yīng)的node的waitStatus改為CANCEL
          cancelAcquire(node);
          }
          }

          /**
          獲取鎖失敗時(shí),檢查并更新Node的waitStatus,如果線程需要阻塞,返回true
          */
          private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
          int ws = pred.waitStatus;
          //前驅(qū)節(jié)點(diǎn)的waitStatus是SIGNAL
          if (ws == Node.SIGNAL)
          /*
          * SIGNAL狀態(tài)的節(jié)點(diǎn),釋放鎖后,會喚醒其后繼節(jié)點(diǎn),因?yàn)榇司€程可以安全阻塞
          */
          return true;
          if (ws > 0) {
          /*
          * 前驅(qū)節(jié)點(diǎn)對應(yīng)的線程被取消,CANCELLED=1.需要將取消狀態(tài)的節(jié)點(diǎn)從隊(duì)列中移除知道找到一個不是取消的節(jié)點(diǎn)為止
          */
          do {
          node.prev = pred = pred.prev;
          } while (pred.waitStatus > 0);
          pred.next = node;
          } else {
          /*
          *除了以上情況,通過CAS將前驅(qū)結(jié)點(diǎn)的狀態(tài)設(shè)置為SIGNAL
          */
          compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
          }
          return false;
          }

          /**
          當(dāng)shouldParkAfterFailedAcquire返回true,則調(diào)用此方法阻塞當(dāng)前線程
          */
          private final boolean parkAndCheckInterrupt() {
          //阻塞當(dāng)前線程
          LockSupport.park(this);
          //判斷當(dāng)前線程是否被中斷,如果中斷了返回true。
          return Thread.interrupted();
          }
          復(fù)制代碼

          獨(dú)占式獲取同步狀態(tài)的可以概括為:對于每個得不到鎖的線程,會被包裝成Node,加入同步隊(duì)列尾部,然后自旋判斷自己的前驅(qū)節(jié)點(diǎn)是否是頭結(jié)點(diǎn),如果是就去嘗試獲得鎖,獲取成功就把自己置為頭結(jié)點(diǎn)。如果獲取失敗則繼續(xù)檢查前驅(qū)節(jié)點(diǎn)的狀態(tài):如果狀態(tài)為SIGNAL(-1),則當(dāng)前節(jié)點(diǎn)進(jìn)入阻塞狀態(tài)(前驅(qū)節(jié)點(diǎn)可以喚醒自己,降低自旋開銷)。如果是除了CANCELLED(1)之外的狀態(tài),則修改為SIGNAL(-1)。當(dāng)又新增一個節(jié)點(diǎn)時(shí),首先自旋判斷當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是否為頭結(jié)點(diǎn),如果不是則直接判斷狀態(tài)是否為SIGNAL(-1),是的話就阻塞,如果是除了CANCELLED(1)之外的其他狀態(tài),則修改為SIGNAL(-1)。

          還有release方法以及共享式獲取與釋放同步狀態(tài)留待大家摸索。

          Synchronized

          概述

          synchronized可以保證方法或代碼執(zhí)行時(shí),同一時(shí)刻只有一個線程可以進(jìn)入臨界區(qū),同時(shí)可以保證共享變量的內(nèi)存可見性。Java中每個對象都可以作為鎖,這是Synchronize實(shí)現(xiàn)同步的基礎(chǔ):

          • 普通同步方法:鎖是當(dāng)前實(shí)例對象

          • 靜態(tài)同步方法:鎖是當(dāng)前類的class對象

          • 靜態(tài)代碼塊:鎖是括號內(nèi)的對象

          原理

          代碼使用synchronized加鎖后,編譯后的字節(jié)碼出現(xiàn)monitorenter和monitorexit兩個指令,可以理解為代碼塊執(zhí)行前的加鎖和退出同步時(shí)的解鎖。執(zhí)行monitorenter指令時(shí),線程會為鎖對象關(guān)聯(lián)一個ObjectMonitor對象。每個線程都有兩個ObjectMonitor對象列表,分別為free和used列表,如果當(dāng)前free列表為空,線程將向全局global list請求分配ObjectMonitor。ObjectMonitor的owner、WaitSet、Cxq、EntryList這幾個屬性比較關(guān)鍵。WaitSet、Cxq、EntryList的隊(duì)列元素是包裝線程后的對象-ObjectWaiter。獲取owner的線程即為獲得鎖的線程。
          總之:

          • 線程遇到synchronized同步時(shí),先會進(jìn)入EntryList隊(duì)列中,然后嘗試把owner變量設(shè)置為當(dāng)前線程,同時(shí)monitor中計(jì)數(shù)器的count加1,即獲得對象鎖。否則通過一定次數(shù)的自旋加鎖,失敗則進(jìn)入Cxq隊(duì)列阻塞等待。

          • 線程執(zhí)行完畢將釋放持有的owner,owner變量恢復(fù)為null,count-1,以便其他線程進(jìn)入獲取鎖。

          synchronize修飾方法的原理類似,只不過不是用monitor指令,而是使用ACC_SYNCHRONIZED標(biāo)識方法的同步。

          鎖優(yōu)化

          適應(yīng)性自旋

          線程阻塞的時(shí)候,讓等待的線程不放棄cpu資源,而是執(zhí)行一個自旋(一般是空循環(huán)),這就叫做自旋鎖。
          自旋等待雖然避免了線程切換的開銷,但要占用CPU時(shí)間,因此如果鎖被占用的時(shí)間很短,效果很好,但如果鎖被占用的時(shí)間很長,那么自旋的線程只會白白消耗資源。
          因此自旋等待的時(shí)間必須有一定限制,如果自旋超過一定次數(shù)還是沒有獲取鎖,就應(yīng)該掛起了。

          鎖消除

          虛擬機(jī)運(yùn)行時(shí),對一些代碼上要求同步但是檢測到不會存在共享數(shù)據(jù)競爭的鎖進(jìn)行消除。一般根據(jù)逃逸分析的數(shù)據(jù)支持來作為判定依據(jù)。

          鎖粗化

          原則上我們在編寫代碼時(shí)會將同步塊的作用范圍縮小,這是為了減少鎖等待的時(shí)間。但如果一系列操作頻繁對同一個對象加鎖解鎖,或者加鎖解鎖在循環(huán)體內(nèi),就會擴(kuò)大加鎖范圍。

          輕量級鎖

          輕量級鎖是JDK1.6引入的,作用是在沒有多線程競爭下,減少傳統(tǒng)的重量級鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能消耗。HotSpot虛擬機(jī)的對象頭分為兩部分信息:第一部分用于存儲對象自身的運(yùn)行時(shí)數(shù)據(jù),這部分稱為Mark Word,還有一部分存儲指向方法區(qū)對象類型數(shù)據(jù)的指針。

          加鎖

          在代碼進(jìn)入同步塊時(shí),如果此同步對象沒有被鎖定(鎖標(biāo)志位為01),虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用于存儲對象目前的Mark Word拷貝。然后虛擬機(jī)使用CAS操作將對象的Mark Word更新為指向Lock Record的指針,如果更新成功,那么這個線程就擁有了該對象的鎖,并且對象Mark Word的鎖標(biāo)志位轉(zhuǎn)變?yōu)椤?0”,表示此對象處于輕量級鎖鎖定狀態(tài)。如果失敗,虛擬機(jī)首先會檢查對象的Mark Word是否指向當(dāng)前線程的棧幀,如果是說明當(dāng)前線程已經(jīng)擁有了這個對象的鎖,那就可以直接進(jìn)入同步塊,否則說明這個鎖對象已經(jīng)被其他線程搶占了。如果有兩條以上線程爭用同一個鎖,那輕量級鎖就不再有效,膨脹為重量級鎖,鎖標(biāo)志位變?yōu)椤?0”。

          解鎖

          解鎖也是CAS操作。如果對象的Mark Word仍指向線程的鎖記錄,那就用CAS把對象當(dāng)前的Mark Word和線程中復(fù)制的Displaces Mark Word替換回來,如果替換成功,這個同步過程就完成了。如果替換失敗,說明有其他線程嘗試獲取該鎖,那就要在釋放鎖同時(shí),喚醒被掛起的線程。

          偏向鎖

          偏向鎖也是JDK1.6引入的優(yōu)化,目的是消除數(shù)據(jù)在無競爭情況下的同步原語。如果說輕量級鎖是在無競爭下使用CAS消除同步使用的互斥量,那偏向鎖就是無競爭下把整個同步都消除。當(dāng)鎖對象第一次被線程獲取時(shí),虛擬機(jī)將對象頭的標(biāo)志位設(shè)為“01”,即偏向模式,同時(shí)使用CAS把獲取到這個鎖的線程ID記錄在對象的Mark Word中,如果CAS成功,持有偏向鎖的線程以后每次進(jìn)入這個同步塊,可以不進(jìn)行任何同步操作。當(dāng)有另外一個線程嘗試獲取這個鎖時(shí),偏向模式結(jié)束。
          HotSpot實(shí)現(xiàn)的JVM在64位機(jī)器的Mark Word信息:

          鎖升級

          鎖升級:new->偏向鎖->輕量級鎖(自旋鎖)->重量級鎖。

          Volatile

          原理

          • 保證可見性,不保證原子性

          • 禁止指令重排序

          • 底層使用內(nèi)存屏障實(shí)現(xiàn)

          內(nèi)存語義

          • 當(dāng)寫一個volatile變量時(shí),JMM會把該線程對應(yīng)的本地內(nèi)存中的共享變量值立即刷新到主內(nèi)存中。

          • 當(dāng)讀一個volatile變量時(shí),JMM會把線程本地變量的值置為無效,從主內(nèi)存中讀取。

          重排序規(guī)則

          • 如果第一個操作是volatile讀,不管第二個操作是什么,都不允許重排序。這個操作確保volatile讀之后的操作不會被編譯器重排序到volatile讀之前。

          • 如果第二個操作是volatile寫,不管第一個操作是什么,都不允許重排序。這個操作確保volatile寫之前的操作不會被編譯器重排序到volaile寫之后。

          volatile底層是通過插入內(nèi)存屏障實(shí)現(xiàn)的:

          • 在每一個volatile寫操作前插入一個StoreStore屏障。

          • 在每一個volatile寫操作后插入一個StoreLoad屏障。

          • 在每一個volatile讀操作后插入一個LoadLoad屏障。

          • 在每一個volatile讀操作后插入一個LoadStore屏障。

          Lock

          ReentrantLock

          ReentrantLock重入鎖,是實(shí)現(xiàn)Lock接口的一個類,支持重入性,表示能夠?qū)蚕碣Y源重復(fù)加鎖,即當(dāng)前線程再次獲取該鎖不會被阻塞。還支持公平鎖和非公平鎖兩種方式。

          重入性的實(shí)現(xiàn)原理

          要想支持重入性,就要解決兩個問題:

          • 在線程獲取鎖時(shí),如果已經(jīng)獲取鎖的線程是當(dāng)前線程則直接獲取成功。

          • 由于鎖會被獲取n次,那么只有當(dāng)鎖被釋放同樣的n次之后,該鎖才算完全釋放成功。

          對于第一個問題,我們來看看ReentrantLock的實(shí)現(xiàn)

          final boolean nonfairTryAcquire(int acquires) {
          final Thread current = Thread.currentThread();
          int c = getState();
          //如果該鎖未被任何線層占有,該鎖能被當(dāng)前線程獲取
          if (c == 0) {
          if (compareAndSetState(0, acquires)) {
          setExclusiveOwnerThread(current);
          return true;
          }
          }
          //如果被占有,檢查占有線程是否為當(dāng)前線程
          else if (current == getExclusiveOwnerThread()) {
          //再次獲取,計(jì)數(shù)加一
          int nextc = c + acquires;
          if (nextc < 0) // overflow
          throw new Error("Maximum lock count exceeded");
          setState(nextc);
          return true;
          }
          return false;
          }
          復(fù)制代碼

          這段代碼邏輯很簡單,如果該鎖已經(jīng)被線程占有了繼續(xù)檢查占有線程的是否為當(dāng)前線程,如果是,同步狀態(tài)+1,表示可以再次獲取成功。再來看下釋放

          protected final boolean tryRelease(int releases) {
          //同步狀態(tài)減1
          int c = getState() - releases;
          //如果不是當(dāng)前線程,拋異常
          if (Thread.currentThread() != getExclusiveOwnerThread())
          throw new IllegalMonitorStateException();
          boolean free = false;
          if (c == 0) {
          //只有當(dāng)同步狀態(tài)為0時(shí),鎖成功被釋放,返回true
          free = true;
          setExclusiveOwnerThread(null);
          }
          //鎖未被完全釋放,返回false
          setState(c);
          return free;
          }
          復(fù)制代碼

          重入鎖的釋放必須等同步狀態(tài)為0時(shí)才算成功釋放,否則鎖仍未釋放。

          公平鎖與非公平鎖

          ReentrantLock支持非公平鎖和公平鎖,公平性是針對獲取鎖而言的,如果一個鎖是公平的,那么鎖的獲取順序就應(yīng)該符合請求上的絕對時(shí)間順序,滿足FIFO。默認(rèn)是非公平鎖。

          public ReentrantLock(boolean fair) {
          sync = fair ? new FairSync() : new NonfairSync();
          }
          復(fù)制代碼

          我們看看公平鎖的處理:

          protected final boolean tryAcquire(int acquires) {
          final Thread current = Thread.currentThread();
          int c = getState();
          if (c == 0) {
          if (!hasQueuedPredecessors() &&
          compareAndSetState(0, acquires)) {
          setExclusiveOwnerThread(current);
          return true;
          }
          }
          else if (current == getExclusiveOwnerThread()) {
          int nextc = c + acquires;
          if (nextc < 0)
          throw new Error("Maximum lock count exceeded");
          setState(nextc);
          return true;
          }
          return false;
          }
          復(fù)制代碼

          這段代碼和非公平鎖唯一的區(qū)別就是增加了hasQueuedProdecessors()的判斷,該方法是用來判斷當(dāng)前節(jié)點(diǎn)在同步隊(duì)列中是否有前驅(qū)節(jié)點(diǎn)的判斷,如果有前驅(qū)節(jié)點(diǎn)說明有線程比當(dāng)前線程更早請求資源,根據(jù)公平性,當(dāng)前線程請求資源失敗。

          ReentrantReadWriteLock

          在一些讀多寫少的業(yè)務(wù)場景,如果僅僅是讀數(shù)據(jù)的話并不會影響數(shù)據(jù)正確性,如果依然使用獨(dú)占鎖的話,就會出現(xiàn)性能瓶頸。針對這種讀多寫少的情況,java提供了讀寫鎖ReentrantReadWriteLock。讀寫鎖允許同一時(shí)刻被多個線程訪問,但是在寫線程訪問時(shí),所有的讀線程和其他寫線程必須阻塞。

          • 公平性選擇:支持非公平性和公平性。

          • 重入性:支持重入,讀鎖獲取后能再次獲取,寫鎖獲取后能再次獲取寫鎖,同時(shí)也能獲取讀鎖。

          • 鎖降級:遵循獲取寫鎖,獲取讀鎖再釋放寫鎖的次序,寫鎖能降級成為讀鎖。

          鎖降級

          鎖降級指的寫鎖降級為讀鎖,即先獲取寫鎖、獲取讀鎖再釋放寫鎖的過程,目的是保證數(shù)據(jù)的可見性。假設(shè)有兩個線程A和B,若線程A獲取到寫鎖,不獲取讀鎖而是直接釋放寫鎖,這時(shí)線程B獲取了寫鎖并修改了數(shù)據(jù),那么線程A無法知道線程B的數(shù)據(jù)更新。如果線程A獲取讀鎖,即遵循鎖降級的步驟,則線程B將會被阻塞,直到線程A使用數(shù)據(jù)并釋放讀鎖之后,線程B才能獲取寫鎖進(jìn)行數(shù)據(jù)更新。

          • 當(dāng)有線程獲取讀鎖后,不允許其他線程獲取寫鎖

          • 當(dāng)有線程獲取寫鎖后,不允許其他線程獲取讀鎖和寫鎖。

          • 寫鎖能降級成為讀鎖,讀鎖無法升級為寫鎖。

          Condition

          跟Object的監(jiān)視器方法一樣,Condition接口也提供了類似方法,配合Lock可以實(shí)現(xiàn)等待/通知模式。

          對比項(xiàng)Object監(jiān)視器方法Condition
          前置條件獲取對象的鎖調(diào)用Lock.lock()獲取鎖,然后調(diào)用lock.newCondition()獲取Condition對象
          調(diào)用方式直接調(diào)用Object.wait()直接調(diào)用condition.wait()
          等待隊(duì)列個數(shù)一個多個
          當(dāng)前線程釋放鎖并進(jìn)入等待狀態(tài)支持支持
          當(dāng)前線程釋放鎖并進(jìn)入等待狀態(tài),在等待狀態(tài)不響應(yīng)中斷不支持支持
          當(dāng)前線程釋放鎖并進(jìn)入超時(shí)等待狀態(tài)支持支持
          當(dāng)前線程釋放鎖并進(jìn)入等待狀態(tài)到將來某個時(shí)間不支持支持
          喚醒等待隊(duì)列中的一個線程支持支持
          喚醒等待隊(duì)列中的全部線程支持支持

          并發(fā)工具類

          CountdownLatch

          CountdownLatch是通過一個計(jì)數(shù)器實(shí)現(xiàn)的,當(dāng)我們在new一個CountdownLatch對象的時(shí)候,需要傳入計(jì)數(shù)器的值,該值表示線程的數(shù)量,每當(dāng)一個線程完成任務(wù)后,計(jì)數(shù)器的值就會減1,當(dāng)計(jì)數(shù)器的值變?yōu)?時(shí),就表示所有線程均已完成任務(wù),然后就可以恢復(fù)等待的線程繼續(xù)執(zhí)行了。
          和CyclicBarrier的區(qū)別:

          • CountdownLatch的作用是允許一個或多個線程等待其他線程完成后繼續(xù)執(zhí)行。而CyclicBarrier則是允許多個線程相互等待。

          • CountdownLatch的計(jì)數(shù)器無法被重置,CyclicBarrier的計(jì)數(shù)器可以被重置后使用。

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

          CountdownLatch僅提供了一個構(gòu)造方法

          public CountDownLatch(int count) {
          if (count < 0) throw new IllegalArgumentException("count < 0");
          this.sync = new Sync(count);
          }
          復(fù)制代碼

          再來看看Sync,是CountDownLatch的一個內(nèi)部類

          private static final class Sync extends AbstractQueuedSynchronizer {
          private static final long serialVersionUID = 4982264981922014374L;

          Sync(int count) {
          setState(count);
          }

          //獲取同步狀態(tài)
          int getCount() {
          return getState();
          }

          //嘗試獲取同步狀態(tài)
          protected int tryAcquireShared(int acquires) {
          return (getState() == 0) ? 1 : -1;
          }

          //嘗試釋放同步狀態(tài)
          protected boolean tryReleaseShared(int releases) {
          for (;;) {
          int c = getState();
          if (c == 0)
          return false;
          int nextc = c-1;
          if (compareAndSetState(c, nextc))
          return nextc == 0;
          }
          }
          }
          復(fù)制代碼

          CountDownLatch內(nèi)部通過共享鎖實(shí)現(xiàn):

          • 在創(chuàng)建CountDownLatch實(shí)例時(shí),傳遞一個int參數(shù):count,該參數(shù)為計(jì)數(shù)器的初始值,也可以理解為共享鎖可以獲取的總次數(shù)。

          • 當(dāng)某個線程調(diào)用await()方法,首先判斷count的值是否為0,如果不為0則會一直等待直到為0為止。

          • 當(dāng)其他線程調(diào)用countdown()方法時(shí),則執(zhí)行釋放共享鎖狀態(tài),count-1。

          • 注意CountDownLatch不能回滾重置。

          CyclicBarrier

          CyclicBarrier是一個同步輔助類,它允許一組線程相互等待,直到達(dá)到某個公共屏障點(diǎn)。在涉及一組大小固定的線程的程序里,這些線程必須不時(shí)的相互等待。因?yàn)镃yclicBarrier在釋放等待線程后可以重用,因此成為循環(huán)屏障。

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

          看下CyclicBarrier的定義

          private final ReentrantLock lock = new ReentrantLock();

          private final Condition trip = lock.newCondition();

          //parties變量表示攔截線程的總數(shù)量
          private final int parties;

          //barrierCommand變量為CyclicBarrier接收的Runnable命令,用于在線程到達(dá)屏障時(shí),優(yōu)先執(zhí)行barrierCommand,用于處理更加復(fù)雜的業(yè)務(wù)場景。
          private final Runnable barrierCommand;

          //generation變量表示CyclicBarrier的更新?lián)Q代
          private Generation generation = new Generation();
          復(fù)制代碼

          可以看出CyclicBarrier內(nèi)部使用可重入鎖ReentrantLock和Condition。它有兩個構(gòu)造函數(shù)

            /**
          創(chuàng)建一個新的CyclicBarrier,它將在給定數(shù)量的參與者(線程)處于等待狀態(tài)時(shí)啟動,并在啟動barrier時(shí)執(zhí)行給定的屏障操作,該操作由最后一個進(jìn)入barrier的線程執(zhí)行。
          */
          public CyclicBarrier(int parties, Runnable barrierAction) {
          if (parties <= 0) throw new IllegalArgumentException();
          this.parties = parties;
          this.count = parties;
          this.barrierCommand = barrierAction;
          }
          /**
          創(chuàng)建一個新的CyclicBarrier,它將在給定數(shù)量的參與者(線程)處于等待狀態(tài)時(shí)啟動,但它不會在啟動barrier時(shí)執(zhí)行預(yù)定義的操作。
          */
          public CyclicBarrier(int parties) {
          this(parties, null);
          }
          復(fù)制代碼

          CyclicBarrier是怎么讓線程達(dá)到屏障后處于等待狀態(tài)的呢?使用await()方法,每個線程調(diào)用await()方法后告訴CyclicBarrier自己到達(dá)了屏障,然后當(dāng)前線程被阻塞。當(dāng)所有線程都到達(dá)了屏障,阻塞結(jié)束,所有線程都可繼續(xù)執(zhí)行后續(xù)邏輯。

          public int await(long timeout, TimeUnit unit)
          throws InterruptedException,
          BrokenBarrierException,
          TimeoutException {
          return dowait(true, unit.toNanos(timeout));
          }


          private int dowait(boolean timed, long nanos)
          throws InterruptedException, BrokenBarrierException,
          TimeoutException {
          //獲取鎖
          final ReentrantLock lock = this.lock;
          lock.lock();
          try {
          //分代
          final Generation g = generation;

          //當(dāng)前generation已損壞,拋出BrokenBarrierException異常
          if (g.broken)
          throw new BrokenBarrierException();
          //如果線程中斷,終止CyclicBarrier
          if (Thread.interrupted()) {
          breakBarrier();
          throw new InterruptedException();
          }

          //進(jìn)來一個線程,count-1
          int index = --count;
          //如果count==0表示所有線程均已到達(dá)屏障,可以觸發(fā)barrierCommand任務(wù)
          if (index == 0) { // tripped
          boolean ranAction = false;
          try {
          final Runnable command = barrierCommand;
          if (command != null)
          command.run();
          ranAction = true;
          //喚醒所有等待線程,并更新generation
          nextGeneration();
          return 0;
          } finally {
          //如果barrierCommand執(zhí)行失敗,終止CyclicBarrier
          if (!ranAction)
          breakBarrier();
          }
          }


          for (;;) {
          try {
          //如果不是超時(shí)等待,則調(diào)用Condition.await()方法等待
          if (!timed)
          trip.await();
          else if (nanos > 0L)
          //如果是超時(shí)等待,則調(diào)用Condition.awaitNanos()等待
          nanos = trip.awaitNanos(nanos);
          } catch (InterruptedException ie) {
          if (g == generation && ! g.broken) {
          breakBarrier();
          throw ie;
          } else {
          Thread.currentThread().interrupt();
          }
          }

          if (g.broken)
          throw new BrokenBarrierException();

          //generation已經(jīng)更新,返回Index
          if (g != generation)
          return index;
          //超時(shí)等待并且時(shí)間已經(jīng)到了,終止CyclicBarrier,并拋出超時(shí)異常
          if (timed && nanos <= 0L) {
          breakBarrier();
          throw new TimeoutException();
          }
          }
          } finally {
          //釋放鎖
          lock.unlock();
          }


          復(fù)制代碼

          應(yīng)用

          CyclicBarrier適用于多線程合并的場景,用于多線程計(jì)算數(shù)據(jù),最后合并計(jì)算結(jié)果的應(yīng)用場景

          public class CyclicBarrierTest {

          private static CyclicBarrier cyclicBarrier;

          private static final Integer THREAD_COUNT = 10;

          static class CyclicBarrierThread implements Runnable {
          @Override
          public void run() {
          System.out.println(Thread.currentThread().getName()+"到教室了");
          try {
          cyclicBarrier.await();
          } catch (Exception e) {
          e.printStackTrace();
          }
          }
          }

          public static void main(String [] args) {
          cyclicBarrier = new CyclicBarrier(THREAD_COUNT, new Runnable() {
          @Override
          public void run() {
          System.out.println("同學(xué)們都到齊了,開始上課吧...");
          }
          });

          for (int i=0; i< THREAD_COUNT; i++) {
          Thread thread = new Thread(new CyclicBarrierThread());
          thread.start();
          }

          }
          }
          復(fù)制代碼

          Semophore

          Semophore是一個控制訪問多個共享資源的計(jì)數(shù)器,和CountdownLatch一樣,本質(zhì)上是一個共享鎖,維護(hù)了一個許可集。以停車場為例:假設(shè)停車場有5個停車位,一開始車位都空著,先后來了3輛車,車位夠,安排進(jìn)去停車。然后又來了3輛車,這時(shí)由于只有兩個車位,所以只能停兩輛,有1輛需要在外面等候,直到停車場有空位。從程序來看,停車場就是信號量Semophore,許可集為5,車輛為線程,每來一輛車,許可數(shù)-1,但必須>=0,否則線程就要阻塞(車輛等待)。如果有一輛車開出,則許可數(shù)+1,然后喚醒一個線程(可以放進(jìn)一輛車)。

          public class SemaphoreTest {

          static class Parking {

          private Semaphore semaphore;

          Parking(int count) {
          semaphore = new Semaphore(count);
          }

          public void park() {
          try {
          //獲取信號量
          semaphore.acquire();
          long time = (long) (Math.random()*10+1);
          System.out.println(Thread.currentThread().getName()+"進(jìn)入停車場停車,停車時(shí)間:"+time+"秒");
          //模擬停車時(shí)間
          Thread.sleep(time);
          System.out.println(Thread.currentThread().getName()+"開出停車場...");
          } catch (InterruptedException e) {
          e.printStackTrace();
          } finally {
          //釋放信號量(跟lock的用法差不多)
          semaphore.release();
          }
          }
          }

          static class Car implements Runnable{

          private Parking parking;

          Car(Parking parking) {
          this.parking = parking;
          }

          /**
          * 每輛車相當(dāng)于一個線程,線程的任務(wù)就是停車
          */
          @Override
          public void run() {
          parking.park();
          }
          }

          public static void main(String [] args) {
          //假設(shè)有3個停車位
          Parking parking = new Parking(3);

          //這時(shí)候同時(shí)來了5輛車,只有3輛車可以進(jìn)去停車,其余2輛車需要等待有空余車位之后才能進(jìn)去停車。
          for (int i=0; i<5; i++) {
          Thread thread = new Thread(new Car(parking));
          thread.start();
          }
          }
          }
          復(fù)制代碼

          Exchanger

          Exchanger是一個同步器,這個類的主要作用是交換數(shù)據(jù)。Exchanger類似CyclicBarrier,CyclicBarrier是一個單向柵欄,而Exchanger是一個雙向柵欄,線程1到達(dá)柵欄后,首先觀察有沒有其他線程已經(jīng)到達(dá)柵欄,如果沒有就會等待。如果已經(jīng)有其他線程(比如線程2)到達(dá)了,就會以成對方式交換信息,因此Exchanger適合兩個線程之間的數(shù)據(jù)交換。

          并發(fā)容器

          CopyOnWriteArrayList

          COW的設(shè)計(jì)思想

          如果簡單使用讀寫鎖ReentrantReadWriteLock,當(dāng)寫鎖被獲取后,讀寫線程被阻塞,只有當(dāng)寫鎖被釋放后讀線程才有機(jī)會獲取到鎖從而讀取到最新的數(shù)據(jù)。但是讀線程想任何時(shí)候都可以獲取到最新的數(shù)據(jù)。COW就是通過Copy-On-Write,即寫時(shí)復(fù)制的思想來通過延時(shí)更新的策略實(shí)現(xiàn)數(shù)據(jù)的最終一致性,并且保證讀線程之間不阻塞。
          COW就是當(dāng)我們往一個容器添加元素時(shí),不直接往當(dāng)前容器添加,而是先將當(dāng)前容器copy,復(fù)制出一個新容器,然后往新容器添加元素,添加完元素后,再將原容器的引用指向新的容器。所以CopyOnWrite是一種讀寫分離的思想,延時(shí)更新的策略是通過在寫的時(shí)候針對的是不同的數(shù)據(jù)容器來實(shí)現(xiàn)的,放棄數(shù)據(jù)實(shí)時(shí)性,實(shí)現(xiàn)最終一致性。

          源碼解讀

          CopyOnWriteArrayList內(nèi)部維護(hù)的就是一個數(shù)組

          /** The array, accessed only via getArray/setArray. */
          private transient volatile Object[] array;
          復(fù)制代碼

          且該數(shù)組用volatile修飾,保證可見性,看下add()方法

          public boolean add(E e) {
          final ReentrantLock lock = this.lock;
          //1. 使用Lock,保證寫線程在同一時(shí)刻只有一個
          lock.lock();
          try {
          //2. 獲取舊數(shù)組引用
          Object[] elements = getArray();
          int len = elements.length;
          //3. 創(chuàng)建新的數(shù)組,并將舊數(shù)組的數(shù)據(jù)復(fù)制到新數(shù)組中
          Object[] newElements = Arrays.copyOf(elements, len + 1);
          //4. 往新數(shù)組中添加新的數(shù)據(jù)
          newElements[len] = e;
          //5. 將舊數(shù)組引用指向新的數(shù)組
          setArray(newElements);
          return true;
          } finally {
          lock.unlock();
          }
          }
          復(fù)制代碼

          add方法需要注意以下幾點(diǎn):

          • 采用ReentrantLock,保證同一時(shí)刻只有一個寫線程進(jìn)行數(shù)組的復(fù)制,否則的話內(nèi)存中會有多個被復(fù)制的數(shù)據(jù)。

          • 數(shù)組引用是volatile修飾的,因此將舊數(shù)組引用指向新數(shù)組,根據(jù)volatile的happens-before原則,寫線程對數(shù)組引用的修改對讀線程是不可見的。

          • 由于寫數(shù)據(jù)的時(shí)候是在新的數(shù)組中插入數(shù)據(jù),從而保證讀寫是在兩個不同的數(shù)據(jù)容器中進(jìn)行。

          ConcurrentSkipListMap

          ConcurrentSkipListMap內(nèi)部使用跳表的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),跳表就是一個多層鏈表,底層是一個普通鏈表,然后逐層減少,通常通過一個簡單的算法實(shí)現(xiàn)每一層的元素是下一層元素的二分之一,這樣當(dāng)搜索元素時(shí)從最頂層開始搜索,可以說是另一種形式的二分查找。理論上它的查找、插入和刪除的時(shí)間復(fù)雜度都是O(logN)。

          插入值為15的元素

          插入后



          作者:寫代碼的強(qiáng)哥
          鏈接:https://juejin.cn/post/6945263312934273055
          來源:稀土掘金
          著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。



          瀏覽 42
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  狠狠人妻久久久久久综合蜜桃 | 青青草成人免费自拍视频 | 国产资源影音先锋 | 亚洲第一网站在线观看 | 色综合综合色 |