Java併發編程實戰(chapter_1)原子性

来源:https://www.cnblogs.com/1024Community/archive/2018/03/11/8542938.html
-Advertisement-
Play Games

這是閱讀《Java編髮編程實戰》這本Java多線程領域的寶典書籍的自我總結與融匯貫通的過程。現在看到了第二部分的第七章,我自己先在我們幾個人中,做一個開頭,把自己學習到的分享出來。現在只是多線程原子性總結了出來,今天陸續吧可見性和不變性都總結出來,貼上來。這些學習,都算是基礎夯實的過程,再多的框架,... ...


混混噩噩看了很多多線程的書籍,一直認為自己還不夠資格去閱讀這本書。有種要高登大堂的感覺,被各種網路上、朋友、同事一頓外加一頓的宣傳與傳頌,多多少少再自我內心中產生了一種敬畏感。2月28好開始看了之後,發現,其實完全沒這個必要。除了翻譯的爛之外(一大段中文下來,有時候你就會罵娘:這tm想說的是個shen me gui),所有的,多線程所必須掌握的知識點,深入點,全部涵蓋其中,只能說,一書在手,萬線程不愁那種!當然,你必須要全部讀懂,並融匯貫通之後,才能有的效果。我推薦,看這本書的中文版本,不要和哪一段的冗長的字句在那過多的糾纏,儘量一段一段的讀,然後獲取這一段中最重要的那句話,否則你會陷入中文閱讀理解的怪圈,而懷疑你的高中語文老師是不是體育老師客串的!!我舉個例子:13頁第八段,我整段讀了三遍硬是沒想明白前面那麼多的文字,是乾什麼用的,就是最後一句話才是核心:告訴你,線程安全性,最正規的定義應該是什麼!(情允許我,向上交的幾個翻譯此書的,所謂的“教授”致敬,在你們的引領下,使我們的意志與忍受力更上了一個臺階,人生更加完美!)

一、多線程開發所要平衡的幾個點

看了很多次的目錄,外加看了第一部分,發現,要想做好多線程的開發,無非就是平衡好以下的幾點

  • 安全性
  • 活躍性
    • 無限迴圈問題
    • 死鎖問題
    • 饑餓問題
    • 活鎖問題(這個還沒具體的瞭解到)
  • 性能要求
    • 吞吐量的問題
    • 可伸縮性的問題

二、多線程開發所要關註的開發點

要想平很好以上幾點,書中循序漸進的將多線程開發最應該修煉的幾個點,娓娓道來:

  • 原子性
    • 先檢查後執行
    • 原子類
    • 加鎖機制
  • 可見性
    • 重排
    • 非64位寫入問題
    • 對象的發佈
    • 對象的封閉
  • 不變性

在一本國人自己寫的,介紹線程工具api的書中,看到了這麼一句話:外練原子,內練可見。感覺這幾點如果在多線程中尤為重要。我在有贊,去年還記得上線多門店的那天凌晨,最後項目啟動報一個類載入的錯誤,一堆人過來看問題,基德大神站在攀哥的後面,最後淡淡的說了句:已經很明顯是可見性問題了,加上volatile,不行的話,我把代碼吃了!!可以見得,多線程這幾個點,在“居家旅行”,生活工作中是多麼的常見與重要!不出問題不要緊,只要一齣,就會是頭痛的大問題,因為你根本不好排查根本原因在這。所以我們需要平時就練好功底,儘量避免多線程問題的出現!而不是一味的用框架啊用框架、摞代碼啊摞代碼!

三、原子性下麵的安全問題

1. 下麵代碼有什麼問題呢?

public class UnsafeConuntingFactorizer implements Servlet{
    private long count = 0;
    private long getCount(){
        return count;
    }
    public void service(ServletRequest req, ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        ++count;
        encodeIntoResponse(resp,factor);
    }
}

思考:如何讓一個普普通通的類變得線程安全呢?一個類什麼叫做有狀態,而什麼又叫做無狀態呢?

2. 解答上面代碼

  • 一個請求的方法,實例都是一個,所以每次請求都會訪問同一個對象
  • 每個請求,使用一個線程,這就是典型的多線程模型
  • count是一個對象狀態屬性,被多個線程共用
  • ++count並非一次原子操作(分成:複製count->對複製體修改->使用複製體回寫count,三個步奏)
  • 多個線程有可能多次修改count值,而結果卻相同

3. 使用原子類解決上面代碼問題

public class UnsafeConuntingFactorizer implements Servlet{
    private final AtomicLong count = new AtomicLong(0);
    private long getCount(){
        return count.get();
    }
    public void service(ServletRequest req, ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        count.incrementAndGet();//使用了新的原子類的原子方法
        encodeIntoResponse(resp,factor);
    }
}

4. 原子類也不是萬能的

//在複雜的場景下,使用多個原子類的對象
public class UnsafeConuntingFactorizer implements Servlet{
    private final AtomicReference<BigInteger> lastNumber 
        = new AtomicReference<BigInteger>();
    private final AtomicReference<BigInteger[]> lastFactors 
        = new AtomicReference<BigInteger[]>();


    public void service(ServletRequest req, ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        if(i.equals(lastNumber.get())){//先判斷再處理,並沒有進行同步,not safe!
            encodeIntoResponse(resp,lastFactors.get());
        }else{
            BigInteger[] factors = factor(i);
            lastNumer.set(i);
            lastFactors.set(factors);
            encodeIntoResponse(resp, factors);
        }
    }
}

思考:什麼叫做複合型操作?

5. 先列舉一個我們常見的複合型操作

public class LazyInitRace {
    private ExpensiveObject instace = null;
    public ExpensiveObject getInstace(){
        if(instace == null){
            instace = new ExpensiveObject();
        }
        return instace;
    }
}

看好了,這就是我們深惡痛絕的一段代碼!如果這段代碼還分析不了的,對不起,出門左轉~

6. 提高“先判斷再處理”的警覺性

  • 如果沒有同步措施,直接對一個狀態進行判斷,然後設值的,都是不安全的
  • if操作和下麵代碼快中的代碼,遠遠不是原子的
  • 如果if判斷完之後,接下來線程掛起,其他線程進入判斷流程,又是同樣的狀態,同樣進入if語句塊
  • 當然,只有一個線程執行的程式,請忽略(那還叫能用的程式嗎?)

7. 性能的問題來了

//在複雜的場景下,使用多個原子類的對象
public class UnsafeConuntingFactorizer implements Servlet{
    private final AtomicReference<BigInteger> lastNumber 
        = new AtomicReference<BigInteger>();
    private final AtomicReference<BigInteger[]> lastFactors 
        = new AtomicReference<BigInteger[]>();

    //這下子總算同步了!
    public synchronized void service(ServletRequest req, ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        if(i.equals(lastNumber.get())){//先判斷再處理,並沒有進行同步,not safe!
            encodeIntoResponse(resp,lastFactors.get());
        }else{
            BigInteger[] factors = factor(i);
            lastNumer.set(i);
            lastFactors.set(factors);
            encodeIntoResponse(resp, factors);
        }
    }
}

思考:有沒有種“關公揮大刀,一砍一大片”的感覺?

8. 上訴代碼解析

  • 加上了synchronized關鍵字的確解決了多線程訪問,類安全性問題
  • 可是每次都是一個線程進行計算,所有請求變成了串列
  • 請求量低於100/s其實都還能接受,可是再高的話,這就完全有問題的代碼了
  • 性能問題,再網路裡面,是永痕的心病~

9. 一段針對原子性、性能問題的解決方案

//在複雜的場景下,使用多個原子類的對象
public class CacheFactorizer implements Servlet{
    private BigInteger lastNumber;
    private BigInteger[] lastFactors ;
    private long hits;
    private long cacheHits;

    public synchronized long getHits(){
        return hits;
    }
    public synchronized double getCacheHitRadio(){
        return (double) cacheHits / (double) hits;
    }

    public void service(ServletRequest req, ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = null;
        synchronized (this){
            ++hits;
            if(i.equals(lastNumber)){
                ++cacheHits;
                factors = lastFactors.clone();
            }
        }
        if (factors == null){
            factors = factor(i);
            synchronized (this){
                lastNumer = i;
                lastFactors = factors.clone();
            }
        }
        encodeIntoResponse(resp, factors);
    }
}

在修改狀態值的時候,才進行加鎖,平時對狀態值的讀操作可以不用加鎖,當然,最耗時的計算過程,也是要同步的,這種情況下,才會進一步提高性能。


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

-Advertisement-
Play Games
更多相關文章
  • js模擬form表單提交數據, js模擬a標簽點擊跳轉,避開使用window.open引起來的瀏覽器阻止問題 ...
  • js強大的日期格式化,timestamp支持10位或13位的時間戳,或是時間字元串,同時支持android ios的處理,不只是日期的格式化還有其它方法,比如獲 獲取某月有多少天 、獲取某個日期在這一年的第幾周、一年中的第幾天、一周中的第幾天等方法 ...
  • 一、jQuery操作DOM 內部插入操作: append(content|fn):向每個匹配的元素內部追加內容。 prepend(content):向每個匹配的元素內部前置內容。 外部插入操作: after(content|fn):在每個匹配的元素之後插入內容。 before(content|fn) ...
  • 學到原型的時候感覺頭都大了/(ㄒoㄒ)/~~ 尤其是ptototype和__proto__ 傻傻分不清 通過多番查找資料,根據自己的理解,總結如下: 一、構造函數: 構造函數:通過new關鍵字可以用來創建特定類型的對象的函數。比如像Object和Array,兩者屬於內置的原生的構造函數,在運行時會自 ...
  • 在之前一篇使用nginx搭建高可用的解決方案的時候,很多同學會問,如果nginx掛掉怎麼辦,比如下麵這張圖: 你可以清楚的看到,如果192.168.2.100這台機器掛掉了,那麼整個集群就下線了,這個問題該怎麼解決呢??? 簡單的想想確實不大好處理,因為你 的webBrowser總得要訪問一個ip地 ...
  • 一、安裝和部署 1、服務端安裝 1、官網下載(官方網站 https://www.mongodb.org/downloads/#production),傻瓜式安裝,註意修改安裝路徑。 安裝完成後的目錄結構: bin中,mongo.exe 為客戶端,mongod.exe 為資料庫: 2、配置環境變數 2 ...
  • 基準時間限制:3 秒 空間限制:131072 KB 分值: 320 難度:7級演算法題 收藏 關註 基準時間限制:3 秒 空間限制:131072 KB 分值: 320 難度:7級演算法題 基準時間限制:3 秒 空間限制:131072 KB 分值: 320 難度:7級演算法題 收藏 關註 收藏 關註 莫比烏 ...
  • ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...