熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

多線程編程您不知道的5件事

2022-06-13   來源: Java高級技術 
    多線程編程向來不容易但很少有 Java? 開發人員能夠忽視多線程編程和支持它的 Java 平台庫我們臨時學習線程在需要時向我們的工具箱添加新的技巧和技術以這種方式構建和運行適當的應用程序是可行的但是您可以做的不止這些理解 Java 編譯器的線程處理特性和 JVM 將有助於您編寫更高效性能更好的 Java 代碼
   
    在這篇文章中我將通過同步方法volatile 變量和原子類介紹多線程編程的一些更隱晦的方面我的討論特別關注於這些構建如何與 JVM 和 Java 編譯器交互以及不同的交互如何影響 Java 應用程序的性能
   
    同步方法或同步代碼塊?
   
    您可能偶爾會思考是否要同步化這個方法調用還是只同步化該方法的線程安全子集在這些情況下知道 Java 編譯器何時將源代碼轉化為字節代碼會很有用它處理同步方法和同步代碼塊的方式完全不同
   
    當 JVM 執行一個同步方法時執行中的線程識別該方法的 method_info 結構是否有 ACC_SYNCHRONIZED 標記設置然後它自動獲取對象的鎖調用方法最後釋放鎖如果有異常發生線程自動釋放鎖
   
    另一方面同步化一個方法塊會越過 JVM 對獲取對象鎖和異常處理的內置支持要求以字節代碼顯式寫入功能如果您使用同步方法讀取一個方法的字節代碼就會看到有十幾個額外的操作用於管理這個功能清單 展示用於生成同步方法和同步代碼塊的調用
   
    清單 兩種同步化方法
   

  

 
    synchronizedMethodGet() 方法生成以下字節代碼
   

  

 
    這裡是來自 synchronizedBlockGet() 方法的字節代碼
   

  

 
    創建同步代碼塊產生了 行的字節碼而創建同步方法僅產生了
   
    ThreadLocal 變量
   
    如果您想為一個類的所有實例維持一個變量的實例將會用到靜態類成員變量如果您想以線程為單位維持一個變量的實例將會用到線程局部變量ThreadLocal 變量與常規變量的不同之處在於每個線程都有其各自初始化的變量實例這通過 get() 或 set() 方法予以評估
   
    比方說您在開發一個多線程代碼跟蹤器其目標是通過您的代碼惟一標識每個線程的路徑挑戰在於您需要跨多個線程協調多個類中的多個方法如果沒有 ThreadLocal這會是一個復雜的問題當一個線程開始執行時它需要生成一個惟一的令牌來在跟蹤器中識別它然後將這個惟一的令牌傳遞給跟蹤中的每個方法
   
    使用 ThreadLocal事情就變得簡單多了線程在開始執行時初始化線程局部變量然後通過每個類的每個方法訪問它保證變量將僅為當前執行的線程托管跟蹤信息在執行完成之後線程可以將其特定的蹤跡傳遞給一個負責維護所有跟蹤的管理對象
   
    當您需要以線程為單位存儲變量實例時使用 ThreadLocal 很有意義


   
    Volatile 變量
   
    我估計大約有一半的 Java 開發人員知道 Java 語言包含 volatile 關鍵字當然其中只有 % 知道它的確切含義有更少的人知道如何有效使用它簡言之使用 volatile 關鍵字識別一個變量意味著這個變量的值會被不同的線程修改要完全理解 volatile 關鍵字的作用首先應當理解線程如何處理非易失性變量
   
    為了提高性能Java 語言規范允許 JRE 在引用變量的每個線程中維護該變量的一個本地副本您可以將變量的這些 線程局部 副本看作是與緩存類似在每次線程需要訪問變量的值時幫助它避免檢查主存儲器
   
    不過看看在下面場景中會發生什麼兩個線程啟動第一個線程將變量 A 讀取為 第二個線程將變量 A 讀取為 如果變量 A 從 變為 第一個線程將不會知道這個變化因此會擁有錯誤的變量 A 的值但是如果將變量 A 標記為 volatile那麼不管線程何時讀取 A 的值它都會回頭查閱 A 的原版拷貝並讀取當前值
   
    如果應用程序中的變量將不發生變化那麼一個線程局部緩存比較行得通不然知道 volatile 關鍵字能為您做什麼會很有幫助
   
    易失性變量與同步化
   
    如果一個變量被聲明為 volatile這意味著它預計會由多個線程修改當然您會希望 JRE 會為易失性變量施加某種形式的同步幸運的是JRE 在訪問易失性變量時確實隱式地提供同步但是有一條重要提醒讀取易失性變量是同步的寫入易失性變量也是同步的但非原子操作不同步
   
    這表示下面的代碼不是線程安全的
   
    myVolatileVar++;
   
    上一條語句也可寫成
   

  

 
    換言之如果一個易失性變量得到更新這樣其值就會在底層被讀取修改並分配一個新值結果將是一個在兩個同步操作之間執行的非線程安全操作然後您可以決定是使用同步化還是依賴於 JRE 的支持來自動同步易失性變量更好的方法取決於您的用例如果分配給易失性變量的值取決於當前值(比如在一個遞增操作期間)要想該操作是線程安全的那麼您必須使用同步化
   
    原子字段更新程序
   
    在一個多線程環境中遞增或遞減一個原語類型時使用在 ncurrentatomic 包中找到的其中一個新原子類比編寫自己的同步代碼塊要好得多原子類確保某些操作以線程安全方式被執行比如遞增和遞減一個值更新一個值添加一個值原子類列表包括 AtomicIntegerAtomicBooleanAtomicLongAtomicIntegerArray 等等
   
    使用原子類的難題在於所有類操作包括 getset 和一系列 getset 操作是以原子態呈現的這表示不修改原子變量值的 read 和 write 操作是同步的不僅僅是重要的 readupdatewrite 操作如果您希望對同步代碼的部署進行更多細粒度控制那麼解決方案就是使用一個原子字段更新程序
   
    使用原子更新
   
    像 AtomicIntegerFieldUpdaterAtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater 之類的原子字段更新程序基本上是應用於易失性字段的封裝器Java 類庫在內部使用它們雖然它們沒有在應用程序代碼中得到廣泛使用但是也沒有不能使用它們的理由
   
    清單 展示一個有關類的示例該類使用原子更新來更改某人正在讀取的書目
   
    清單 Book 類
   

  

 
    Book 類僅是一個 POJO(Java 原生類對象)擁有一個單一字段name


   
    清單 MyObject 類
   

  

 
    正如您所期望的清單 中的 MyObject 類通過 get 和 set 方法公開其 whatAmIReading 屬性但是 set 方法所做的有點不同它不僅僅將其內部 Book 引用分配給指定的 Book(這將使用 清單 中注釋出的代碼來完成)而是使用一個 AtomicReferenceFieldUpdater
   
    AtomicReferenceFieldUpdater
   
    AtomicReferenceFieldUpdater 的 Javadoc 將其定義為
   
    對指定類的指定易失性引用字段啟用原子更新的一個基於映像的實用程序該類旨在用於這樣的一個原子數據結構中即同一節點的若干引用字段獨立地得到原子更新
   
    在 清單 AtomicReferenceFieldUpdater 由一個對其靜態 newUpdater 方法的調用創建該方法接受三個參數
   
    包含字段的對象的類(在本例中為 MyObject)
   
    將得到原子更新的對象的類(在本例中是 Book)
   
    將經過原子更新的字段的名稱
   
    這裡真正的價值在於getWhatImReading 方法未經任何形式的同步便被執行而 setWhatImReading 是作為一個原子操作執行的
   
    清單 展示如何使用 setWhatImReading() 方法並斷定值的變動是正確的
   
    清單 演習原子更新的測試用例
   

  

 
    結束語
   
    多線程編程永遠充滿了挑戰但是隨著 Java 平台的演變它獲得了簡化一些多線程編程任務的支持在本文中我討論了關於在 Java 平台上編寫多線程應用程序您可能不知道的 件事包括同步化方法與同步化代碼塊之間的不同為每個線程存儲運用 ThreadLocal 變量的價值被廣泛誤解的 volatile 關鍵字(包括依賴於 volatile 滿足同步化需求的危險)以及對原子類的錯雜之處的一個簡要介紹


From:http://tw.wingwit.com/Article/program/Java/gj/201311/27308.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.