日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Java并發(fā)之同步器設(shè)計(jì)

在 Java并發(fā)之內(nèi)存模型了解到多進(jìn)程(線程)讀取共享資源的時(shí)候存在競爭條件。

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)建站!專注于網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、重慶小程序開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了蒲江縣免費(fèi)建站歡迎大家使用!

計(jì)算機(jī)中通過設(shè)計(jì)同步器來協(xié)調(diào)進(jìn)程(線程)之間執(zhí)行順序。同步器作用就像登機(jī)安檢人員一樣可以協(xié)調(diào)旅客按順序通過。

在Java中,同步器可以理解為一個(gè)對象,它根據(jù)自身狀態(tài)協(xié)調(diào)線程的執(zhí)行順序。比如鎖(Lock),信號量(Semaphore),屏障(CyclicBarrier),阻塞隊(duì)列(Blocking Queue)。

這些同步器在功能設(shè)計(jì)上有所不同,但是內(nèi)部實(shí)現(xiàn)上有共通的地方。

同步器

同步器的設(shè)計(jì)一般包含幾個(gè)方面:狀態(tài)變量設(shè)計(jì)(同步器內(nèi)部狀態(tài)),訪問條件設(shè)定,狀態(tài)更新,等待方式,通知策略。

訪問條件是控制線程是否能執(zhí)行(訪問共享對象)的條件,它往往與狀態(tài)變量緊密相關(guān)。而通知策略是線程釋放鎖定狀態(tài)后通知其它等待線程的方式,一般有以下幾種情況

  •  通知所有等待的線程。
  •  通知1個(gè)隨機(jī)的N個(gè)等待線程。
  •  通知1個(gè)特定的N個(gè)等待線程

看下面例子,通過鎖方式的同步器

 
 
 
 
  1. public class Lock{ 
  2.   // 狀態(tài)變量 isLocked 
  3.   private boolean isLocked = false; 
  4.   public synchronized void lock() throws InterruptedException{ 
  5.     // 訪問條件 當(dāng)isLocked=false 時(shí)獲得訪問權(quán)限否則等待 
  6.     while(isLocked){ 
  7.       // 阻塞等待 
  8.       wait(); 
  9.     } 
  10.     //狀態(tài)更新 線程獲得訪問權(quán)限 
  11.     isLocked = true; 
  12.   } 
  13.   public synchronized void unlock(){ 
  14.     //狀態(tài)更新 線程釋放訪問權(quán)限 
  15.     isLocked = false; 
  16.     // 通知策略 object.notify | object.notifyAll 
  17.     notify();  
  18.   } 
  19. }

我們用計(jì)數(shù)信號量控制同時(shí)執(zhí)行操作活動數(shù)。這里模擬一個(gè)連接池。

 
 
 
 
  1. public class PoolSemaphore { 
  2.       // 狀態(tài)變量 actives 計(jì)數(shù)器 
  3.     private int actives = 0; 
  4.     private int max; 
  5.     public PoolSemaphore(int max) { 
  6.         this.max = max; 
  7.     } 
  8.     public synchronized void acquire() throws InterruptedException { 
  9.         //訪問條件 激活數(shù)小于最大限制時(shí),獲得訪問權(quán)限否則等待 
  10.         while (this.actives == max) wait(); 
  11.         //狀態(tài)更新 線程獲得訪問權(quán)限 
  12.         this.actives++; 
  13.         // 通知策略 object.notify | object.notifyAll 
  14.         this.notify(); 
  15.     } 
  16.     public synchronized void release() throws InterruptedException { 
  17.         //訪問條件 激活數(shù)不為0時(shí),獲得訪問權(quán)限否則等待 
  18.         while (this.actives == 0) wait(); 
  19.          //狀態(tài)更新 線程獲得訪問權(quán)限 
  20.         this.actives--; 
  21.         // 通知策略 object.notify | object.notifyAll 
  22.         this.notify(); 
  23.     } 
  24. }

原子指令

同步器設(shè)計(jì)里面,最重要的操作邏輯是“如果滿足條件,以更新狀態(tài)變量來標(biāo)志線程獲得或釋放訪問權(quán)限”,該操作應(yīng)具備原子性。

比如test-and-set 計(jì)算機(jī)原子指令,意思是進(jìn)行條件判斷滿足則設(shè)置新值。

 
 
 
 
  1. function Lock(boolean *lock) {  
  2.     while (test_and_set(lock) == 1);  
  3. }

另外還有很多原子指令 fetch-and-add compare-and-swap,注意這些指令需硬件支持才有效。

同步操作中,利用計(jì)算機(jī)原子指令,可以避開鎖,提升效率。java中沒有 test-and-set 的支持,不過 java.util.concurrent.atomic 給我們提供了很多原子類API,里面支持了 getAndSet 和compareAndSet 操作。

看下面例子,主要在區(qū)別是等待方式不一樣,上面是通過wait()阻塞等待,下面是無阻塞循環(huán)。

 
 
 
 
  1. public class Lock{ 
  2.   // 狀態(tài)變量 isLocked 
  3.   private AtomicBoolean isLocked = new AtomicBoolean(false); 
  4.   public void lock() throws InterruptedException{ 
  5.     // 等待方式 變?yōu)樽孕却?nbsp;
  6.     while(!isLocked.compareAndSet(false, true)); 
  7.     //狀態(tài)更新 線程獲得訪問權(quán)限 
  8.     isLocked.set(true); 
  9.   } 
  10.   public synchronized void unlock(){ 
  11.     //狀態(tài)更新 線程釋放訪問權(quán)限 
  12.     isLocked.set(false); 
  13.   } 
  14. }

關(guān)于阻塞擴(kuò)展說明

阻塞意味著需要將進(jìn)程或線程狀態(tài)進(jìn)行轉(zhuǎn)存,以便還原后恢復(fù)執(zhí)行。這種操作是昂貴繁重,而線程基于進(jìn)程之上相對比較輕量。線程的阻塞在不同編程平臺實(shí)現(xiàn)方式也有所不同,像Java是基于JVM運(yùn)行,所以它由JVM完成實(shí)現(xiàn)。

在《Java Concurrency in Practice》中,作者提到

競爭性同步可能需要OS活動,這增加了成本。當(dāng)爭用鎖時(shí),未獲取鎖的線程必須阻塞。 JVM可以通過旋轉(zhuǎn)等待(反復(fù)嘗試獲取鎖直到成功)來實(shí)現(xiàn)阻塞,也可以通過操作系統(tǒng)掛起阻塞的線程來實(shí)現(xiàn)阻塞。哪種效率更高取決于上下文切換開銷與鎖定可用之前的時(shí)間之間的關(guān)系。對于短暫的等待,最好使用自旋等待;對于長時(shí)間的等待,最好使用暫停。一些JVM基于對過去等待時(shí)間的分析數(shù)據(jù)來自適應(yīng)地在這兩者之間進(jìn)行選擇,但是大多數(shù)JVM只是掛起線程等待鎖定。

從上面可以看出JVM實(shí)現(xiàn)阻塞兩種方式

  •  旋轉(zhuǎn)等待(spin-waiting),簡單理解是不暫停執(zhí)行,以循環(huán)的方式等待,適合短時(shí)間場景。
  •  通過操作系統(tǒng)掛起線程。

JVM中通過 -XX: +UseSpinning 開啟旋轉(zhuǎn)等待, -XX: PreBlockSpi =10指定最大旋轉(zhuǎn)次數(shù)。

AQS

AQS是AbstractQueuedSynchronizer簡稱。本節(jié)對AQS只做簡單闡述,并不全面。

java.util.concurrent包中的 ReentrantLock,CountDownLatch,Semaphore,CyclicBarrier等都是基于是AQS同步器實(shí)現(xiàn)。

狀態(tài)變量是用 int state 來表示,狀態(tài)的獲取與更新通過以下API操作。 

 
 
 
 
  1. int getState() 
  2.     void setState(int newState) 
  3.     boolean compareAndSetState(int expect, int update)

該狀態(tài)值在不同API中有不同表示意義。比如ReentrantLock中表示持有鎖的線程獲取鎖的次數(shù),Semaphore表示剩余許可數(shù)。

關(guān)于等待方式和通知策略的設(shè)計(jì)

AQS通過維護(hù)一個(gè)FIFO同步隊(duì)列(Sync queue)來進(jìn)行同步管理。當(dāng)多線程爭用共享資源時(shí)被阻塞入隊(duì)。而線程阻塞與喚醒是通過 LockSupport.park/unpark API實(shí)現(xiàn)。

它定義了兩種資源共享方式。

  •  Exclusive(獨(dú)占,只有一個(gè)線程能執(zhí)行,如ReentrantLock)
  •  Share(共享,多個(gè)線程可同時(shí)執(zhí)行,如Semaphore/CountDownLatch)

每個(gè)節(jié)點(diǎn)包含waitStatus(節(jié)點(diǎn)狀態(tài)),prev(前繼),next(后繼),thread(入隊(duì)時(shí)線程),nextWaiter(condition隊(duì)列的后繼節(jié)點(diǎn))

waitStatus 有以下取值。

  •  CANCELLED(1) 表示線程已取消。當(dāng)發(fā)生超時(shí)或中斷,節(jié)點(diǎn)狀態(tài)變?yōu)槿∠?,之后狀態(tài)不再改變。
  •  SIGNAL(-1) 表示后繼節(jié)點(diǎn)等待前繼的喚醒。后繼節(jié)點(diǎn)入隊(duì)時(shí),會將前繼狀態(tài)更新為SIGNAL。
  •  CONDITION(-2) 表示線程在Condition queue 里面等待。當(dāng)其他線程調(diào)用了Condition.signal()方法后,CONDITION狀態(tài)的節(jié)點(diǎn)將從 Condition queue 轉(zhuǎn)移到 Sync queue,等待獲取鎖。
  •  PROPAGATE(-3) 在共享模式下,當(dāng)前節(jié)點(diǎn)釋放后,確保有效通知后繼節(jié)點(diǎn)。
  •  (0) 節(jié)點(diǎn)加入隊(duì)列時(shí)的默認(rèn)狀態(tài)。

AQS 幾個(gè)關(guān)鍵 API

  •  tryAcquire(int) 獨(dú)占方式下,嘗試去獲取資源。成功返回true,否則false。
  •  tryRelease(int) 獨(dú)占方式下,嘗試釋放資源,成功返回true,否則false。
  •  tryAcquireShared(int) 共享方式下,嘗試獲取資源。返回負(fù)數(shù)為失敗,零和正數(shù)為成功并表示剩余資源。
  •  tryReleaseShared(int) 共享方式下,嘗試釋放資源,如果釋放后允許喚醒后續(xù)等待節(jié)點(diǎn)返回true,否則false。
  •  isHeldExclusively() 判斷線程是否正在獨(dú)占資源。

acquire(int arg)

 
 
 
 
  1. public final void acquire(int arg) { 
  2.         if ( 
  3.           // 嘗試直接去獲取資源,如果成功則直接返回 
  4.           !tryAcquire(arg) 
  5.             && 
  6.             //線程阻塞在同步隊(duì)列等待獲取資源。等待過程中被中斷,則返回true,否則false 
  7.             acquireQueued( 
  8.               // 標(biāo)記該線程為獨(dú)占方式,并加入同步隊(duì)列尾部。 
  9.               addWaiter(Node.EXCLUSIVE), arg)  
  10.            ) 
  11.             selfInterrupt(); 
  12.     }

release(int arg)

 
 
 
 
  1. public final boolean release(int arg) { 
  2.          // 嘗試釋放資源 
  3.        if (tryRelease(arg)) { 
  4.            Node h = head; 
  5.            if (h != null && h.waitStatus != 0) 
  6.              // 喚醒下一個(gè)線程(后繼節(jié)點(diǎn)) 
  7.              unparkSuccessor(h); 
  8.            return true; 
  9.        } 
  10.        return false; 
  11.    } 
 
 
 
 
  1. private void unparkSuccessor(Node node) { 
  2.         .... 
  3.               Node s = node.next; // 找到后繼節(jié)點(diǎn) 
  4.         if (s == null || s.waitStatus > 0) {//無后繼或節(jié)點(diǎn)已取消 
  5.             s = null; 
  6.            // 找到有效的等待節(jié)點(diǎn)  
  7.             for (Node t = tail; t != null && t != node; tt = t.prev) 
  8.                 if (t.waitStatus <= 0) 
  9.                     s = t; 
  10.         } 
  11.         if (s != null) 
  12.             LockSupport.unpark(s.thread); // 喚醒
  13.     }

總結(jié)

本文記錄并發(fā)編程中同步器設(shè)計(jì)的一些共性特征。并簡單介紹了Java中的AQS。


本文題目:Java并發(fā)之同步器設(shè)計(jì)
本文URL:http://m.5511xx.com/article/dhogede.html