Java并發(fā)編程

Java并發(fā)編程

來自Java并發(fā)編程的藝術(shù)
個人博客: http://blog.csdn.net/qq_22329521/article/details/52576788

并發(fā)一定比串行快么外恕?

這個問題肯定是錯的尽棕,并發(fā)比串行慢的原因在于:線程有創(chuàng)建和上下文切換的開銷

上下文切換

即使是單核處理器也支持多線程執(zhí)行代碼地啰,CPU通過給每個線程分配CPU時間片來實現(xiàn)這個機制莹菱。CPU通過時間片分配的算法來循環(huán)執(zhí)行任務(wù),當(dāng)前任務(wù)執(zhí)行一個時間片后會切換到下一個任務(wù)召庞。但是俩檬,在切換前會保持上一個任務(wù)的狀態(tài)窑业,以便下次切換回這個任務(wù)時愿汰,可以再加之這個任務(wù)的狀態(tài)困后。所以任務(wù)從保存到再加載的過程就是一次上下文切換
如何減少上下文切換
--

  • 無鎖并發(fā)編程:多線程競爭鎖衬廷,會引起上下文切換,所以多線程處理數(shù)據(jù)時汽绢,可以用一些辦法避免使用鎖吗跋,如將數(shù)據(jù)的ID按照Hash算法取模分段,不同的線程處理不同段的數(shù)據(jù)
  • CAS算法:Java的Atomic包使用CAS算法來更新數(shù)據(jù)宁昭,而不需要加鎖跌宛。
  • 使用最少線程:避免創(chuàng)建不需要的線程,比如任務(wù)很少积仗,但是創(chuàng)建了很多線程來處理疆拘,這樣會造成大量線程處于等待
  • 協(xié)程:在單行線程中實現(xiàn)多任務(wù)調(diào)度,并在單線程中維持多個任務(wù)的切換

避免死鎖的幾種方式

  • 避免一個線程同時獲取多個鎖
  • 避免一個線程在鎖內(nèi)同時占用多個資源寂曹,盡量保證每個鎖只占用一個資源
  • 嘗試使用定時鎖哎迄,使用lock.tryLock(timeout)來替代使用內(nèi)部鎖機制排拷。
  • 對于數(shù)據(jù)庫鎖孵滞,加鎖和解鎖必須在一個數(shù)據(jù)庫連接中里,否則會出現(xiàn)解鎖失敗的情況弦牡。

資源限制

資源限制指的是程序的執(zhí)行速度受限于計算機硬件資源或軟件資源渺氧,如服務(wù)器的帶寬只有2Mb/s,某個資源的下載速度為1Mb/s,系統(tǒng)啟動10個線程去下載資源旨涝,下載速度不會變成10Mb/s,所以在進行并發(fā)的時候回考慮資源的限制侣背。硬件資源限制有帶寬的上傳/下載速度白华、硬盤的讀寫速度和CPU的處理速度。軟件資源限制有數(shù)據(jù)庫的連接數(shù)和socket連接數(shù)等贩耐。

資源限制引來的問題:為了將代碼執(zhí)行速度加快將代碼中串行執(zhí)行的部分變成并發(fā)執(zhí)行弧腥,因為資源受限,仍然在串行執(zhí)行憔杨,這時候程序不僅不會加快鸟赫,反而會變慢,因為增加了上下文切換和資源調(diào)度的時間。

如何解決資源限制問題:可以使用集群并行執(zhí)行程序抛蚤,既然單機的資源有限台谢,那么可以讓程序在多機上運行,比如使用ODPS岁经、Hadoop或者自己搭個服務(wù)器集群朋沮,不同的機器處理不同的數(shù)據(jù),可以通過“數(shù)據(jù)ID%機器數(shù)”缀壤,計算得到一個機器編號樊拓,然后由對應(yīng)編號的機器處理這個數(shù)據(jù),對于軟件資源受限塘慕,可以使用資源池來復(fù)用如使用連接池將數(shù)據(jù)庫和Socket連接復(fù)用筋夏,或者在調(diào)用對方webservice接口獲取數(shù)據(jù)只建立一個連接。

Java并發(fā)機制的底層實現(xiàn)原理

Java代碼在編譯后會變成Java字節(jié)碼图呢,字節(jié)碼被類加載器加載到JVM里条篷,JVM執(zhí)行字節(jié)碼,最終需要轉(zhuǎn)化為匯編指令在CPU上執(zhí)行蛤织,Java所使用的并發(fā)機制依賴于JVM的實現(xiàn)和CPU的指令

volatile的應(yīng)用

volatile是輕量級的synchronized赴叹,在多處理器并發(fā)中保證了共享變量的可見性,可見性是指當(dāng)一個線程修改了一個共享變量指蚜,另一個線程能讀到修改的值乞巧,它不會引起線程上下文切換和調(diào)度

volatile在java代碼轉(zhuǎn)換為匯編代碼 會多了一個Lock前綴的指令,在多核處理器下發(fā)生兩件事情

  • 將當(dāng)前處理器緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存
  • 將這個協(xié)會內(nèi)存的操作會使得其他CPU力緩存了該內(nèi)存的地址的數(shù)據(jù)無效

為了提高處理速度摊鸡,處理器不直接和內(nèi)存通信绽媒,而是將系統(tǒng)內(nèi)存的數(shù)據(jù)讀到內(nèi)部緩存(L1,L2或其他)后再進行操作柱宦,但操作完不知道何時回寫到內(nèi)存些椒,如果聲明了volatile的變量進行寫操作,JVM就會向處理器發(fā)送一條Lock前綴的指令掸刊,將這個變量所在緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存免糕,但是就是寫會內(nèi)存,如果其他處理器緩存的值還是舊的忧侧,再執(zhí)行計算操作就會有問題石窑,所以在多處理器下為了保證各個處理器的緩存是一致,就會執(zhí)行緩存一致性協(xié)議蚓炬,每個處理器通過嗅探在總線上傳播的數(shù)據(jù)來檢查自己的緩存值是否過期松逊,當(dāng)處理器發(fā)現(xiàn)自己緩存行所對應(yīng)的內(nèi)存地址被修改,就會將當(dāng)前處理器緩存行設(shè)置為無效肯夏,當(dāng)處理器對數(shù)據(jù)進行修改操作经宏,會重新從系統(tǒng)內(nèi)存讀到處理器緩存中

synchronized的和應(yīng)用

javase1.6 對synchronized進行各種優(yōu)化犀暑,過去被人稱為重量級鎖。
java每個對象都是鎖

  • 對于普通同步方法烁兰,鎖就是實例對象
  • 對于靜態(tài)同步方法耐亏,鎖就是當(dāng)前類的Class對象
  • 對于同步代碼塊,鎖就是Synchonized括號里配置的對象

JVM基于進入和退出Monitor對象來實現(xiàn)方法同步和代碼塊同步
synchronized用的鎖是存在Java對象頭里的沪斟。Java對象頭里的Mark Word力默認儲存對象的HashCode广辰,分代年齡和鎖標記位。在運行期間主之,MarkWord儲存的數(shù)據(jù)會隨著鎖標記位的變化而變化

Javase1.6中 鎖一共有4種狀態(tài)择吊,級別從低到高為:無鎖狀態(tài)、偏向鎖狀態(tài)槽奕、輕量級鎖狀態(tài)和重量級鎖狀態(tài)几睛,這幾個狀態(tài)會隨著競爭情況逐漸升級。鎖可以升級但不能降級史翘,目的是為了提高獲得鎖和釋放鎖的效率

  1. 偏向鎖:大多數(shù)情況下枉长,鎖不僅不存在多線程競爭,而且總是同一個線程多次獲取琼讽,為了讓線程獲得鎖的代價更低引入偏向鎖。當(dāng)某一線程訪問同步塊時洪唐,會在對象頭和棧幀中的瑣記錄里存儲鎖偏向的線程ID钻蹬,以后該線程在進入該同步塊的時候,不需要再次使用CAS原子操作進行加鎖和解鎖凭需,只需要簡單的測試一下對象頭中的Mark Word是否存在指向當(dāng)前線程的偏向鎖问欠。如果測試成功,則表示獲得鎖粒蜈,否則檢測是否設(shè)置有偏向鎖顺献,如果沒有,則使用CAS競爭鎖枯怖,否則偏向鎖指向該線程注整。
  2. 輕量級鎖:線程執(zhí)行同步塊之前,會在線程私有的棧幀中開辟用于存儲鎖記錄的空間度硝,稱為Displaced Mark Word肿轨。然后線程嘗試將對象Mark Word的替換為指向Displaced Mark Word記錄的指針,如果成功蕊程,那么當(dāng)前線程獲得鎖椒袍,如果失敗,那么使用自旋獲得鎖藻茂。

原子操作的實現(xiàn)原理

  1. 處理器實現(xiàn)原子操作:使用總線鎖保證原子性驹暑,使用緩存鎖保證原子性(修改內(nèi)存地址玫恳,緩存一致性機制:阻止同時修改由2個以上的處理器緩存的內(nèi)存區(qū)域數(shù)據(jù))
  2. JAVA實現(xiàn)原子操作:循環(huán)使用CAS實現(xiàn)原子操作
public class Counter {
    private AtomicInteger atomicI = new AtomicInteger(0);
    private int i = 0;
    public static void main(String[] args) {
        final Counter cas = new Counter();
        List<Thread> ts = new ArrayList<Thread>(600);
        long start = System.currentTimeMillis();
        for (int j = 0; j < 100; j++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        cas.count();
                        cas.safeCount();
                    }
                }
            });
            ts.add(t);
        }

        for (Thread t : ts) {
            t.start();
        }
        // 等待所有線程執(zhí)行完成
        for (Thread t : ts) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println(cas.i);
        System.out.println(cas.atomicI.get());
        System.out.println(System.currentTimeMillis() - start);

    }

    /**
     * 使用CAS實現(xiàn)線程安全計數(shù)器
     */
    private void safeCount() {
        for (;;) {
            int i = atomicI.get();
            boolean suc = atomicI.compareAndSet(i, ++i);
            if (suc) {
                break;
            }
        }
    }
    /**
     * 非線程安全計數(shù)器
     */
    private void count() {
        i++;
    }
}

CAS實現(xiàn)原子操作的三大問題

  1. ABA問題:因為CAS需要在操作值的時候檢查下值有沒有發(fā)生變化,如果沒有發(fā)生變化則更新优俘,但是如果一個值原來是A京办,變成了B,又變成了A兼吓,那么使用CAS進行檢查時會發(fā)現(xiàn)它的值沒有發(fā)生變化臂港,但是實際上卻變化了。ABA問題的解決思路就是使用版本號视搏。在變量前面追加上版本號审孽,每次變量更新的時候把版本號加一,那么A-B-A 就會變成1A-2B-3A浑娜。
  2. 循環(huán)時間長開銷大佑力。自旋CAS如果長時間不成功,會給CPU帶來非常大的執(zhí)行開銷筋遭。如果JVM能支持處理器提供的pause指令那么效率會有一定的提升打颤,pause指令有兩個作用,第一它可以延遲流水線執(zhí)行指令(de-pipeline),使CPU不會消耗過多的執(zhí)行資源漓滔,延遲的時間取決于具體實現(xiàn)的版本编饺,在一些處理器上延遲時間是零。第二它可以避免在退出循環(huán)的時候因內(nèi)存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush)响驴,從而提高CPU的執(zhí)行效率透且。
  3. 只能保證一個共享變量的原子操作。當(dāng)對一個共享變量執(zhí)行操作時豁鲤,我們可以使用循環(huán)CAS的方式來保證原子操作秽誊,但是對多個共享變量操作時,循環(huán)CAS就無法保證操作的原子性琳骡,這個時候就可以用鎖锅论,或者有一個取巧的辦法,就是把多個共享變量合并成一個共享變量來操作楣号。比如有兩個共享變量i=2,j=a最易,合并一下ij=2a,然后用CAS來操作ij竖席。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性耘纱,你可以把多個變量放在一個對象里來進行CAS操作。

Java內(nèi)存模型

內(nèi)容較多涉及到內(nèi)存模型毕荐、重排序
http://blog.csdn.net/ccit0519/article/details/11241403(內(nèi)容較多也比較詳細介紹)
double_check的問題

public class DoubleCheckedlocking{
  private static Instance instance;
  public static Instance getInstance(){
     if(instance==null){
      synchronized(DoubleCheckedlocking.class){
       if(instance==null)
         instance=new Instance();
       }
     }
  }
}
//根據(jù)重排序可能會出現(xiàn)的問題
instance=new Instance()常見一個對象可以分成三步
memory=allocate(),//1.分配對象的內(nèi)存空間
ctorInstance(memory)//2.初始化對象
instance=memory //3.設(shè)置Instance指向剛分配的內(nèi)存地址
如果2,3 重排序顛倒后  if語句就可以是引用是上存在但是對象還未被初始化束析,所以 可以給Instance加上一個volatile因為內(nèi)存屏障的緣故

Java中的鎖

鎖是用來控制多個線程訪問共享資源的方式,一般來說憎亚,一個鎖能夠防止多個線程同時訪問共享資源员寇,但是有些鎖可以運行多個線程并發(fā)的訪問共享資源弄慰,比如讀寫鎖。Lock接口和synchronized可以通過獲取鎖和釋放鎖蝶锋,但是前者比后者更具擴展性陆爽。
Lock是一個接口,定義了鎖獲取和釋放的基本操作
Lock和synchronized區(qū)別

  • Lock接口可以嘗試非阻塞地獲取鎖 當(dāng)前線程嘗試獲取鎖扳缕。如果這一時刻鎖沒有被其他線程獲取到慌闭,則成功獲取并持有鎖。
  • Lock接口能被中斷地獲取鎖 與synchronized不同躯舔,獲取到鎖的線程能夠響應(yīng)中斷驴剔,當(dāng)獲取到的鎖的線程被中斷時,中斷異常將會被拋出粥庄,同時鎖會被釋放丧失。
  • Lock接口在指定的截止時間之前獲取鎖,如果截止時間到了依舊無法獲取鎖惜互,則返回布讹。

Lock接口的APi

  • void lock() 獲取鎖,調(diào)用該方法當(dāng)前線程將會獲取鎖,當(dāng)鎖獲取后训堆,該方法將返回描验。
  • void lockInterruptibly() throws InterruptedException 可中斷獲取鎖,與lock()方法不同之處在于該方法會響應(yīng)中斷坑鱼,即在鎖的獲取過程中可以中斷當(dāng)前線程
  • boolean tryLock() 嘗試非阻塞的獲取鎖挠乳,調(diào)用該方法立即返回,true表示獲取到鎖
  • boolean tryLock(long time,TimeUnit unit) throws InterruptedException 超時獲取鎖姑躲,以下情況會返回:時間內(nèi)獲取到了鎖,時間內(nèi)被中斷盟蚣,時間到了沒有獲取到鎖黍析。
  • void unlock() 釋放鎖

隊列同步器(AQS)

隊列同步器AbstractQueuedSynchronizer(以下簡稱同步器),是用來構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架屎开,它使用了一個int成員變量表示同步狀態(tài)阐枣,通過內(nèi)置的FIFO隊列來完成資源獲取線程的排隊工作。同步器的主要使用方式是繼承奄抽,子類通過繼承同步器并實現(xiàn)它的抽象方法來管理同步狀態(tài)蔼两,在抽象方法的實現(xiàn)過程中免不了要對同步狀態(tài)進行更改,這時就需要使用同步器提供的3個方法(getState()逞度、setState(int newState)和compareAndSetState(int expect,int update))來進行操作额划,因為它們能夠保證狀態(tài)的改變是安全的。同步器既可以支持獨占式地獲取同步狀態(tài)档泽,也可以支持共享式地獲取同步狀態(tài)俊戳,這樣就可以方便實現(xiàn)不同類型的同步組件(ReentrantLock揖赴、ReentrantReadWriteLock和CountDownLatch等)。同步器是實現(xiàn)鎖(也可以是任意同步組件)的關(guān)鍵抑胎,在鎖的實現(xiàn)中聚合同步器燥滑,利用同步器實現(xiàn)鎖的語義“⑻樱可以這樣理解二者之間的關(guān)系:鎖是面向使用者的铭拧,它定義了使用者與鎖交互的接口(比如可以允許兩個線程并行訪問),隱藏了實現(xiàn)細節(jié)恃锉;同步器面向的是鎖的實現(xiàn)者搀菩,它簡化了鎖的實現(xiàn)方式,屏蔽了同步狀態(tài)管理淡喜、線程的排隊秕磷、等待與喚醒等底層操作。鎖和同步器很好地隔離了使用者和實現(xiàn)者所需關(guān)注的領(lǐng)域

enter image description here

enter image description here

public class Mutex implements Lock {
    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        Condition newCondition() {
            return new ConditionObject();
        }
    }

    private final Sync sync = new Sync();

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

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

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

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

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

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

}

以上是獨占式鎖是一個自定義的同步組件炼团,在同一個時刻指運行一個線程占有鎖澎嚣,用戶在使用Mutex并不會直接和內(nèi)部同步器打交道,而是調(diào)用Mutex提供的方法瘟芝,在Mutex的實現(xiàn)中易桃,獲取鎖Lock方法。
同步隊列
--
同步器依賴內(nèi)部的同步隊列(一個FIFO雙向隊列)來完成同步狀態(tài)的管理锌俱。同步隊列中的節(jié)點(Node)用來保存"獲取同步狀態(tài)失敗的線程"引用晤郑、等待狀態(tài)以及前驅(qū)和后繼節(jié)點。


enter image description here

enter image description here

當(dāng)前線程獲取同步狀態(tài)失敗時贸宏,同步器會將當(dāng)前線程造寝、等待狀態(tài)等信息構(gòu)造成為一個節(jié)點(Node)并將其加入同步隊列,同時會”“阻塞”當(dāng)前線程吭练。當(dāng)一個線程成功地獲取了同步狀態(tài)(或者鎖)诫龙,其他線程將無法獲取到同步狀態(tài),轉(zhuǎn)而被構(gòu)造成為節(jié)點并加入到同步隊列中鲫咽,而這個加入隊列的過程必須要保證線程安全签赃。同步器提供了一個基于CAS的設(shè)置尾節(jié)點的方法:compareAndSetTail(Nodeexpect,Nodeupdate),它需要傳遞當(dāng)前線程“認為”的尾節(jié)點和當(dāng)前節(jié)點分尸,只有設(shè)置成功后锦聊,當(dāng)前節(jié)點才正式與之前的尾節(jié)點建立關(guān)聯(lián)。

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

主要邏輯:首先調(diào)用自定義同步器實現(xiàn)的tryAcquire(int arg)方法箩绍,該方法保證線程安全的獲取同步狀態(tài)孔庭,如果同步狀態(tài)獲取失敗,則構(gòu)造同步節(jié)點(獨占式Node.EXCLUSIVE伶选,同一時刻只能有一個線程成功獲取同步狀態(tài))并通過addWaiter(Node node)方法將該節(jié)點加入到同步隊列的尾部史飞,最后調(diào)用acquireQueued(Node node,int arg)方法尖昏,使得該節(jié)點以“死循環(huán)”的方式獲取同步狀態(tài)


enter image description here
//將節(jié)點加入到同步隊列的尾部
private Node addWaiter(Node mode) {
      Node node = new Node(Thread.currentThread(), mode);//“生成節(jié)點”
      // Try the fast path of enq; backup to full enq on failure
      //快速嘗試在尾部添加
      Node pred = tail;
      if (pred != null) {
          node.prev = pred;//先將當(dāng)前節(jié)點node的前驅(qū)指向當(dāng)前tail
          if (compareAndSetTail(pred, node)) {//CAS嘗試將tail設(shè)置為node
              //如果CAS嘗試成功,就說明"設(shè)置當(dāng)前節(jié)點node的前驅(qū)"與"CAS設(shè)置tail"之間沒有別的線程設(shè)置tail成功
              //只需要將"之前的tail"的后繼節(jié)點指向node即可
              pred.next = node;
              return node;
          }
      }
      enq(node);//否則构资,通過死循環(huán)來保證節(jié)點的正確添加
      return node;
  }
private Node enq(final Node node) {
    for (;;) {//通過死循環(huán)來保證節(jié)點的正確添加
        Node t = tail;
        if (t == null) { // Must initialize 同步隊列為空的情況
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {//直到CAS成功為止
                t.next = node;
                return t;//結(jié)束循環(huán)
            }
        }
    }
}

上述代碼通過使用compareAndSetTail(Node expect,Node update)來確保節(jié)點能夠被線程安全添加抽诉,如果使用普通的LinkedList來維護節(jié)點之間的關(guān)系,那么當(dāng)一個線程獲取到同步狀態(tài)吐绵,而其他多個線程由于調(diào)用tryAcquire(int arg)方法獲取同步狀態(tài)失敗而并發(fā)被添加到LinkedList迹淌,LinkedList將難以保證Node的正確添加
在enq(final Node node)方法中,同步器通過“死循環(huán)”來保證節(jié)點的正確添加己单,在“死循環(huán)”中只有通過CAS將節(jié)點設(shè)置成為尾節(jié)點之后唉窃,當(dāng)前線程才能從該方法返回,否則纹笼,當(dāng)前線程不斷地嘗試設(shè)置纹份。可以看出廷痘,enq(final Node node)方法將并發(fā)添加節(jié)點的請求通過CAS變得“串行化”了蔓涧。
節(jié)點自旋
--
節(jié)點進入同步隊列之后,就進入了一個自旋的過程笋额,每個節(jié)點(或者說是線程)都在自省地觀察元暴,當(dāng)條件滿足,獲取到了同步狀態(tài)兄猩,就可以從這個自旋過程中退出茉盏,否則依舊留在這個自旋過程中。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {//無限循環(huán)
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {//前驅(qū)節(jié)點是首節(jié)點且獲取到了同步狀態(tài)
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;//從自旋中退出
            }
            if (shouldParkAfterFailedAcquire(p, node) &&//判斷獲取同步狀態(tài)失敗后是否需要阻塞
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

共享式同步狀態(tài)獲取與釋放

共享式獲取與獨占式獲取最主要的區(qū)別在于同一時刻能否有多個線程同時獲取到同步狀態(tài)枢冤。
以文件的讀寫為例鸠姨,如果一個程序在對文件進行讀操作,那么這一時刻對于該文件的寫操作均被阻塞淹真,而讀操作能夠同時進行享怀。寫操作要求對資源的獨占式訪問,而讀操作可以是共享式訪問

enter image description here

調(diào)用同步器的acquireShared(int arg)方法可以共享式地獲取同步狀態(tài)趟咆。

  public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
      private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

在acquireShared(int arg)方法中,同步器調(diào)用tryAcquireShared(int arg)方法嘗試獲取同步狀態(tài)梅屉,tryAcquireShared(int arg)方法返回值為int類型值纱,當(dāng)返回值大于等于0時,表示能夠獲取到同步狀態(tài)坯汤。因此虐唠,在共享式獲取的自旋過程中,成功獲取到同步狀態(tài)并退出自旋的條件就是tryAcquireShared(int arg)方法返回值大于等于0惰聂。
在doAcquireShared(int arg)方法的自旋過程中疆偿,如果當(dāng)前節(jié)點的前驅(qū)為頭節(jié)點時咱筛,嘗試獲取同步狀態(tài),如果返回值大于等于0杆故,表示該次獲取同步狀態(tài)成功并從自旋過程中退出迅箩。
重入鎖
--
它表示該鎖能夠支持一個線程對資源的重復(fù)加鎖。除此之外处铛,該鎖的還支持獲取鎖時的公平和非公平性選擇
之前的例子饲趋,當(dāng)一個線程調(diào)用Mutex的lock()方法獲取鎖之后,如果再次調(diào)用lock()方法撤蟆,則該線程將會被自己所阻塞奕塑,原因是Mutex在實現(xiàn)tryAcquire(int acquires)方法時沒有考慮占有鎖的線程再次獲取鎖的場景,而在調(diào)用tryAcquire(int acquires)方法時返回了false家肯,導(dǎo)致該線程被阻塞龄砰。簡單地說,Mutex是一個不支持重進入的鎖讨衣。而synchronized關(guān)鍵字隱式的支持重進入换棚,比如一個synchronized修飾的遞歸方法,在方法執(zhí)行時值依,執(zhí)行線程在獲取了鎖之后仍能連續(xù)多次地獲得該鎖圃泡,而不像Mutex由于獲取了鎖,而在下一次獲取鎖時出現(xiàn)阻塞自己的情況愿险。
ReentrantLock雖然沒能像synchronized關(guān)鍵字一樣支持隱式的重進入颇蜡,但是在調(diào)用lock()方法時,已經(jīng)獲取到鎖的線程辆亏,能夠再次調(diào)用lock()方法獲取鎖而不被阻塞风秤。
公平鎖與非公平鎖的比較
--
公平性鎖每次都是從同步隊列中的第一個節(jié)點獲取到鎖,而非公平性鎖出現(xiàn)了一個線程連續(xù)獲取鎖的情況扮叨。
非公平性鎖可能使線程“饑餓”缤弦,當(dāng)一個線程請求鎖時,只要獲取了同步狀態(tài)即成功獲取鎖彻磁。在這個前提下碍沐,剛釋放鎖的線程再次獲取同步狀態(tài)的幾率會非常大,使得其他線程只能在同步隊列中等待衷蜓。
為什么它又被設(shè)定成默認的實現(xiàn)呢累提?非公平性鎖模式下線程上下文切換的次數(shù)少,因此其性能開銷更小磁浇。公平性鎖保證了鎖的獲取按照FIFO原則斋陪,而代價是進行大量的線程切換。非公平性鎖雖然可能造成線程“饑餓”,但極少的線程切換无虚,保證了其更大的吞吐量缔赠。

讀寫鎖

讀寫鎖在同一時刻可以允許多個讀線程訪問,但是在寫線程訪問時友题,所有的讀線程和其他寫線程均被阻塞嗤堰。
讀寫鎖維護了一對鎖,一個讀鎖和一個寫鎖咆爽,通過分離讀鎖和寫鎖梁棠,使得并發(fā)性相比一般的排他鎖有了很大提升。除了保證寫操作對讀操作的可見性以及并發(fā)性的提升之外斗埂,讀寫鎖能夠簡化讀寫交互場景的編程方式符糊。在讀多于寫的情況下,讀寫鎖能夠提供比排它鎖更好的并發(fā)性和吞吐量呛凶。Java并發(fā)包提供讀寫鎖的實現(xiàn)是ReentrantReadWriteLock男娄。

ConcurrentHashMap的實現(xiàn)原理與使用

ConcurrentHashMap是線程安全且高效的HashMap。
為什么使用ConcurrentHashMap的原因

  1. HashMap線程不安全漾稀,在多線程下使用HashMap進行put操作會引起死循環(huán)模闲,導(dǎo)致CPU利用率接近100%,原因在于多線程會導(dǎo)致HashMap的Entry鏈表形成環(huán)形數(shù)據(jù)結(jié)構(gòu)崭捍,一旦形成環(huán)形數(shù)據(jù)結(jié)構(gòu)尸折,Entry的next節(jié)點永遠不為空,就回產(chǎn)生死循環(huán)獲取Entry
  2. HashTable效率低
  3. ConcurrentHashMap的鎖分段技術(shù)可以有效提升并發(fā)訪問率殷蛇,原因在于HashTable在競爭中都是競爭同一把鎖实夹,但是ConcurrentHashMap將數(shù)據(jù)分成一段一段地儲存,然后給每一段數(shù)據(jù)配一把鎖粒梦,當(dāng)一個線程占用鎖訪問其中一段數(shù)據(jù)時候亮航,其他段的數(shù)據(jù)也被其他線程訪問
    具體的實現(xiàn)及原理http://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/

Fork/Join框架

Fork/Join框架是Java7提供了的一個用于并行執(zhí)行任務(wù)的框架, 是一個把大任務(wù)分割成若干個小任務(wù)匀们,最終匯總每個小任務(wù)結(jié)果后得到大任務(wù)結(jié)果的框架缴淋。

enter image description here

工作竊取算法
--
指某個線程從其他隊列里竊取任務(wù)來執(zhí)行。假如我們需要做一個比較大的任務(wù)泄朴,我們可以把這個任務(wù)分割為若干互不依賴的子任務(wù)重抖,為了減少線程間的競爭,于是把這些子任務(wù)分別放到不同的隊列里祖灰,并為每個隊列創(chuàng)建一個單獨的線程來執(zhí)行隊列里的任務(wù)仇哆,線程和隊列一一對應(yīng),比如A線程負責(zé)處理A隊列里的任務(wù)夫植。但是有的線程會先把自己隊列里的任務(wù)干完,而其他線程對應(yīng)的隊列里還有任務(wù)等待處理。干完活的線程與其等著详民,不如去幫其他線程干活延欠,于是它就去其他線程的隊列里竊取一個任務(wù)來執(zhí)行。而在這時它們會訪問同一個隊列沈跨,所以為了減少竊取任務(wù)線程和被竊取任務(wù)線程之間的競爭由捎,通常會使用雙端隊列,被竊取任務(wù)線程永遠從雙端隊列的頭部拿任務(wù)執(zhí)行饿凛,而竊取任務(wù)的線程永遠從雙端隊列的尾部拿任務(wù)執(zhí)行狞玛。優(yōu)點是充分利用線程進行并行計算,并減少了線程間的競爭涧窒。
參考文章
http://blog.csdn.net/ccfeng2008/article/details/49389463
http://blog.csdn.net/qq_16811963/article/details/52171764
http://blog.csdn.net/canot/article/details/52050633
http://www.2cto.com/kf/201608/540926.html
http://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/
http://www.infoq.com/cn/articles/fork-join-introduction/
java并發(fā)編程的藝術(shù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末心肪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子纠吴,更是在濱河造成了極大的恐慌硬鞍,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戴已,死亡現(xiàn)場離奇詭異固该,居然都是意外死亡,警方通過查閱死者的電腦和手機糖儡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門伐坏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人握联,你說我怎么就攤上這事桦沉。” “怎么了拴疤?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵永部,是天一觀的道長。 經(jīng)常有香客問我呐矾,道長苔埋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任蜒犯,我火速辦了婚禮组橄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罚随。我一直安慰自己玉工,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布淘菩。 她就那樣靜靜地躺著遵班,像睡著了一般屠升。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上狭郑,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天腹暖,我揣著相機與錄音,去河邊找鬼翰萨。 笑死脏答,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亩鬼。 我是一名探鬼主播殖告,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼雳锋!你這毒婦竟也來了黄绩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤魄缚,失蹤者是張志新(化名)和其女友劉穎宝与,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冶匹,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡习劫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嚼隘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诽里。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖飞蛹,靈堂內(nèi)的尸體忽然破棺而出谤狡,到底是詐尸還是另有隱情,我是刑警寧澤卧檐,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布墓懂,位于F島的核電站,受9級特大地震影響霉囚,放射性物質(zhì)發(fā)生泄漏捕仔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一盈罐、第九天 我趴在偏房一處隱蔽的房頂上張望榜跌。 院中可真熱鬧,春花似錦盅粪、人聲如沸钓葫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽础浮。三九已至帆调,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間豆同,已是汗流浹背贷帮。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留诱告,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓民晒,卻偏偏與公主長得像精居,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子潜必,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容