最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺談Java并發(fā)之同步器設計

 更新時間:2021年10月22日 08:56:57   作者:onlythinking  
這篇文章主要介紹Java并發(fā)之同步器設計,本文以記錄方式并發(fā)編程中同步器設計的一些共性特征。并簡單介紹了Java中的AQS,需要的朋友可以參考一下文章的詳細內(nèi)容

前言:

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

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

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

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

1、同步器

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

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

  • 通知所有等待的線程。
  • 通知1個隨機的N個等待線程。
  • 通知1個特定的N個等待線程

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

public class Lock{
  // 狀態(tài)變量 isLocked
  private boolean isLocked = false; 
  public synchronized void lock() throws InterruptedException{
    // 訪問條件 當isLocked=false 時獲得訪問權限否則等待
    while(isLocked){
      // 阻塞等待
      wait();
    }
    //狀態(tài)更新 線程獲得訪問權限
    isLocked = true;
  }
  
  public synchronized void unlock(){
    //狀態(tài)更新 線程釋放訪問權限
    isLocked = false;
    // 通知策略 object.notify | object.notifyAll
    notify(); 
  }
}

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

public class PoolSemaphore {
   // 狀態(tài)變量 actives 計數(shù)器
    private int actives = 0;
    private int max;
    public PoolSemaphore(int max) {
        this.max = max;
    }
    public synchronized void acquire() throws InterruptedException {
        //訪問條件 激活數(shù)小于最大限制時,獲得訪問權限否則等待
        while (this.actives == max) wait();
        //狀態(tài)更新 線程獲得訪問權限
        this.actives++;
        // 通知策略 object.notify | object.notifyAll
        this.notify();
    }
    public synchronized void release() throws InterruptedException {
        //訪問條件 激活數(shù)不為0時,獲得訪問權限否則等待
        while (this.actives == 0) wait();
         //狀態(tài)更新 線程獲得訪問權限
        this.actives--;
        // 通知策略 object.notify | object.notifyAll
        this.notify();
    }
}

1.1 原子指令

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

比如test-and-set 計算機原子指令,意思是進行條件判斷滿足則設置新值。

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


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

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

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

public class Lock{
  // 狀態(tài)變量 isLocked
  private AtomicBoolean isLocked = new AtomicBoolean(false);
  public void lock() throws InterruptedException{
    // 等待方式 變?yōu)樽孕却?
    while(!isLocked.compareAndSet(false, true));
    //狀態(tài)更新 線程獲得訪問權限
    isLocked.set(true);
  }
  
  public synchronized void unlock(){
    //狀態(tài)更新 線程釋放訪問權限
    isLocked.set(false);
  }
}

1.2 關于阻塞擴展說明

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

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

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

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

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

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

2、AQS

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

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

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

 int getState()
void setState(int newState)
boolean compareAndSetState(int expect, int update)


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

關于等待方式和通知策略的設計

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

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

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

每個節(jié)點包含waitStatus(節(jié)點狀態(tài)),prev(前繼),next(后繼),thread(入隊時線程),nextWaitercondition隊列的后繼節(jié)點)

waitStatus 有以下取值

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

AQS 幾個關鍵 API

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

2.1 acquire(int arg)

public final void acquire(int arg) {
    if (
      // 嘗試直接去獲取資源,如果成功則直接返回
      !tryAcquire(arg)
        &&
        //線程阻塞在同步隊列等待獲取資源。等待過程中被中斷,則返回true,否則false
        acquireQueued(
          // 標記該線程為獨占方式,并加入同步隊列尾部。
          addWaiter(Node.EXCLUSIVE), arg) 
       )
        selfInterrupt();
}

2.2 release(int arg)

public final boolean release(int arg) {
   // 嘗試釋放資源
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
          // 喚醒下一個線程(后繼節(jié)點)
          unparkSuccessor(h);
        return true;
    }
    return false;
}
private void unparkSuccessor(Node node) {
  ....
     Node s = node.next; // 找到后繼節(jié)點
        if (s == null || s.waitStatus > 0) {//無后繼或節(jié)點已取消
            s = null;
           // 找到有效的等待節(jié)點 
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread); // 喚醒線程
    }

總結:

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

到此這篇關于淺談Java并發(fā)之同步器設計的文章就介紹到這了,更多相關Java并發(fā)之同步器設計內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java使用POI導出Excel(二):多個sheet

    Java使用POI導出Excel(二):多個sheet

    這篇文章介紹了Java使用POI導出Excel的方法,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-10-10
  • org.springframework.beans.BeanInstantiationException異常解決

    org.springframework.beans.BeanInstantiationException異常解決

    本文主要介紹了org.springframework.beans.BeanInstantiationException異常解決,大多數(shù)情況下,這個異常是由于簡單的配置錯誤或者代碼問題導致的,下面就來具體解決一下
    2024-03-03
  • Java servlet后端開發(fā)超詳細教程

    Java servlet后端開發(fā)超詳細教程

    Servlet指在服務器端執(zhí)行的一段Java代碼,可以接收用戶的請求和返回給用戶響應結果,下面這篇文章主要給大家介紹了關于Java.servlet生命周期的相關資料,需要的朋友可以參考下
    2023-02-02
  • 在Spring Boot中如何使用log4j記錄日志

    在Spring Boot中如何使用log4j記錄日志

    這篇文章主要介紹如何在spring boot中引入log4j,以及一些基礎用法,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-02-02
  • 使用Mybatis實現(xiàn)分頁效果示例

    使用Mybatis實現(xiàn)分頁效果示例

    大家好,本篇文章主要講的是使用Mybatis實現(xiàn)分頁效果示例,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12
  • java定時任務Timer和TimerTask使用詳解

    java定時任務Timer和TimerTask使用詳解

    這篇文章主要為大家詳細介紹了java定時任務Timer和TimerTask使用方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-02-02
  • Java ArrayList擴容問題實例詳解

    Java ArrayList擴容問題實例詳解

    這篇文章主要介紹了Java ArrayList擴容問題實例詳解,分享了相關代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下
    2018-02-02
  • spring boot @ResponseBody轉(zhuǎn)換JSON 時 Date 類型處理方法【兩種方法】

    spring boot @ResponseBody轉(zhuǎn)換JSON 時 Date 類型處理方法【兩種方法】

    這篇文章主要介紹了spring boot @ResponseBody轉(zhuǎn)換JSON 時 Date 類型處理方法,主要給大家介紹Jackson和FastJson兩種方式,每一種方法給大家介紹的都非常詳細,需要的朋友可以參考下
    2018-08-08
  • 劍指Offer之Java算法習題精講二叉樹與N叉樹

    劍指Offer之Java算法習題精講二叉樹與N叉樹

    跟著思路走,之后從簡單題入手,反復去看,做過之后可能會忘記,之后再做一次,記不住就反復做,反復尋求思路和規(guī)律,慢慢積累就會發(fā)現(xiàn)質(zhì)的變化
    2022-03-03
  • spring boot實現(xiàn)自動輸出word文檔功能的實例代碼

    spring boot實現(xiàn)自動輸出word文檔功能的實例代碼

    這篇文章主要介紹了spring boot實現(xiàn)自動輸出word文檔功能的實例代碼,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04

最新評論

望都县| 屏东县| 金阳县| 朝阳区| 宿州市| 确山县| 绥江县| 伊金霍洛旗| 阳江市| 双桥区| 鹤峰县| 新邵县| 永州市| 峡江县| 海丰县| 林州市| 洮南市| 邳州市| 丰宁| 郁南县| 诸暨市| 绥芬河市| 安吉县| 金华市| 阿拉善盟| 施秉县| 广东省| 博乐市| 德兴市| 宣武区| 通山县| 黄龙县| 突泉县| 南昌市| 恩平市| 蓝田县| 游戏| 奈曼旗| 登封市| 南投县| 霍邱县|