Java併發AQS原理分析(二)

来源:https://www.cnblogs.com/duzhentong/archive/2018/04/17/8861353.html
-Advertisement-
Play Games

上一篇詳細的分析了獨占模式下如何對線程進行處理:簡單的總結是Java面向用戶提供了鎖的機制,後面的實現使用了一個同步隊列,由於隊列具有先進先出的特點,把每個線程都構造成為隊列中的節點,每個節點定義一個狀態值,符合狀態的節點(線程)才可以有執行的機會,執行完釋放,後面的線程只能是等待著前面的執行結果進 ...


上一篇詳細的分析了獨占模式下如何對線程進行處理:簡單的總結是Java面向用戶提供了鎖的機制,後面的實現使用了一個同步隊列,由於隊列具有先進先出的特點,把每個線程都構造成為隊列中的節點,每個節點定義一個狀態值,符合狀態的節點(線程)才可以有執行的機會,執行完釋放,後面的線程只能是等待著前面的執行結果進行判斷,每個線程的執行都是獨立的,不能有其他的線程干擾,所以在用戶的角度來看線程是在同步的執行的,並且是獨占式的。

共用式和獨占式的區別主要是能否在同一時刻不同的線程獲取到同步狀態

上圖可以很直觀的看出獨占式和共用式的區別。在對一個資源進行訪問的時候,對讀操作是共用式的,而對寫操作是獨占式的。

下麵接著分析共用模式下線程之間是怎麼實現的?
依然找到方法執行的入口,在上一篇我們找到了這幾種方式的頂層方法。

共用模式同步狀態的獲取:

acquireShared()方法中,調用了tryAcquireShared()返回一個狀態值,進行判斷,獲取成功直接返回,失敗進入等待隊列中。

public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

方法執行的順序:

  1. 調用tryAcquireShared()方法嘗試去獲取資源,具體實現是在子類中進行實現,成功直接返回,失敗執行下麵的方法。
  2. 調用doAcquireShared()方法,線程進入等待隊列,等待獲取資源。

tryAcquireShared()方法是在子類中實現的,這裡不需要討論,但是返回值已經是定義好的。方法返回一個int類型的值,當返回值大於0的時候,表示能夠獲取到同步狀態。

doAcquireShared()方法:
在前面分析過的方法這裡不再分析

private void doAcquireShared(int arg) {
        //調用addWaiter方法把線程加入隊列尾
        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);
        }
    }

基本上和獨占式的處理方式一致,根本的體現就是在下麵的這個setHeadAndPropagate()方法
setHeadAndPropagate():

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; 
        setHead(node);
        //如果還有資源剩餘,繼續喚醒後面挨著的線程
        if (propagate > 0 || h == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

在獨占模式下獲取資源,即使資源還有剩餘,隊列中的後一個線程節點也不會被喚醒執行,只有自己占有才是獨占,而共用式的是有剩餘就會給後面。

parkAndCheckInterrupt():

private final boolean parkAndCheckInterrupt() {
        //讓當前對象停止執行,阻塞狀態
        LockSupport.park(this);
        //檢查中斷
        return Thread.interrupted();
    }

cancelAcquire():

private void cancelAcquire(Node node) {
        if (node == null)
            return;
        node.thread = null;
        //獲得前驅結點
        Node pred = node.prev;
        //如果狀態值大於0
        while (pred.waitStatus > 0)
            //移除當前節點的前驅結點
            node.prev = pred = pred.prev;
        //設置節點狀態值為CANCELLED
        node.waitStatus = Node.CANCELLED;
        //如果是尾節點,設置尾節點
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            int ws;
            //當節點既不是尾節點,也不是頭節點的後繼節點時,下麵的這些判斷其實執行的出隊操作起作用的就是compareAndSetNext()方法將pred指向後繼節點
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                //如果node是head的後繼節點,則直接喚醒node的後繼節點
                unparkSuccessor(node);
            }
            node.next = node; // help GC
        }
    }

共用模式同步狀態的釋放:

嘗試釋放資源如果成功,會喚醒後續處於等待狀態中的節點

public final boolean releaseShared(int arg) {
        //嘗試釋放資源
        if (tryReleaseShared(arg)) {
            //喚醒後繼節點
            doReleaseShared();
            return true;
        }
        return false;
    }

doReleaseShared():

private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    //喚醒後繼節點
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                
            }
            if (h == head)                   
                break;
        }
    }

和獨占式的釋放從代碼上看他們的區別是共用式的釋放方法中有一個迴圈。

引用:https://www.cnblogs.com/waterystone/p/4920797.html

上面方法的流程也比較簡單,一句話:釋放掉資源後,喚醒後繼。跟獨占模式下的release()相似,但有一點稍微需要註意:獨占模式下的tryRelease()在完全釋放掉資源(state=0)後,才會返回true去喚醒其他線程,這主要是基於獨占下可重入的考量;而共用模式下的releaseShared()則沒有這種要求,共用模式實質就是控制一定量的線程併發執行,那麼擁有資源的線程在釋放掉部分資源時就可以喚醒後繼等待結點。例如,資源總量是13,A(5)和B(7)分別獲取到資源併發運行,C(4)來時只剩1個資源就需要等待。A在運行過程中釋放掉2個資源量,然後tryReleaseShared(2)返回true喚醒C,C一看只有3個仍不夠繼續等待;隨後B又釋放2個,tryReleaseShared(2)返回true喚醒C,C一看有5個夠自己用了,然後C就可以跟A和B一起運行。而ReentrantReadWriteLock讀鎖的tryReleaseShared()只有在完全釋放掉資源(state=0)才返回true,所以自定義同步器可以根據需要決定tryReleaseShared()的返回值。


在共用模式下,資源有剩餘就會給後面的鄰居,不會自己占有,這是和獨占式的根本區別。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 對於前端初學者來說,css浮動部分的知識是一塊比較難以理解的部分,下麵我將把我學習過程中的心得分享給大家。 導讀: 1.css塊級元素講解 2.css中浮動是如何產生的 3.出現浮動後,如何清除浮動(本文將涉及到多種清除浮動的方法) 博客正文: 1.css塊級元素講解 常見的塊級元素主要有以下幾種: ...
  • “金三銀四,金九銀十”,都是要收穫的季節。面對各種面試題,各種概念、原理都要去記,挺枯燥的。本文是面向面試題和實際使用談一下Promise。 Promise是什麼? Promise是JS非同步編程中的重要概念,非同步抽象處理對象,是目前比較流行Javascript非同步編程解決方案之一。這句話說的很明白了 ...
  • 一、概念 享元模式是對象的結構模式,它以共用的方式高效的支持大量的細粒度對象,減少對象的數量,並達到節約記憶體的目的。 享元對象能夠做到共用的關鍵,主要是區分了內部狀態和外部狀態,內部狀態是對象是在建立時就已確定了,且它不隨環境的改變而有所不同,所以這些內部狀態就可以共用,而外部狀態是會隨著環境的變化 ...
  • 開發部署 Spring Cloud 微服務框架,需要先確定 Spring Cloud 的相關環境版本,主要包含:Spring Cloud、Spring Cloud Netflix、JDK、JRE、Java Version等相關版本,以及環境的配置。 現在最新版本 Java 10、Spring Boo ...
  • layout: post title: 2016 03 22 信息系統實踐手記3 按業務展開的代碼剝離 key: 20160322 tags: 測試 重構 方面編程 優化 持續集成 master PO 開發 剝離 modify_date: 2016 03 22 信息系統實踐手記3 按業務展開的代碼剝 ...
  • 在面向對象的設計中,我們應當遵循以下六大原則,只有掌握了這些原則,才能更好的理解設計模式。 1:單一職責原則(Single Responsibility Principle 簡稱 :SRP) : 就一個類而言,應該僅有一個引起他變化的原因 2:介面隔離原則(Interface Segregation ...
  • layout: post title: 2016 03 11 信息系統實踐手記2 客戶端啟動速度調優思路 key: 20160311 tags: 客戶端 調優 測試 伺服器 GIS WPF 模塊 壓力 瓶頸 modify_date: 2016 03 11 信息系統實踐手記2 客戶端啟動速度調優思路 ...
  • 手把手教你寫網路爬蟲(3) 作者:拓海 摘要:從零開始寫爬蟲,初學者的速成指南! 封面: 介紹 大家好!我們從今天開始學習開源爬蟲框架Scrapy,如果你看過《手把手》系列的前兩篇,那麼今天的內容就非常容易理解了。細心的讀者也許會有疑問,為什麼不學出身名門的Apache頂級項目Nutch,或者人氣飆 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...