首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

破除 Java 迷思(三):原子操作不一定線程安全

     編輯:Java 相關知識

常聞「Java 原子操作線程安全」。依定義原子操作不可中斷,故被視為線程安全。實際上,部分原子操作不保證線程安全。

迷思成因

為減少 synchronized 關鍵字使用(同步有效能損耗,雖現代 JVM 持續優化)。程式設計師追求效率,此議題延續至今。

Java 原子操作範圍

32 位元或以下賦值為原子。32 位平台上,除 double、long(64 位)外,其他基本型別與物件參考(機器指標,通常 32 位)操作均為原子。

小迷思:基本型別大小由語言保證?錯誤。Java 保證的是數值範圍而非 JVM 儲存大小。int 範圍固定,但某 JVM 可能用 32 位實作,另一個用 64 位。重申:所有平台保證的是數值範圍,32 位及以下值操作為原子。

原子操作何時不線程安全?

核心問題:JVM 允許線程在私有工作記憶體保存變數副本,為提升效能不強制每次讀主記憶體。雖賦值原子,但讀寫若針對私有副本而非主記憶體,線程安全不被保證。JVM 此行為亦非保證。

範例分析

class RealTimeClock {
    private int clkID;
    public int clockID() { return clkID; }
    public void setClockID(int id) { clkID = id; }
}

兩線程並發:T1 setClockID(5) 寫入私有記憶體;T2 setClockID(10) 寫入私有記憶體;T1 clockID() 從私有記憶體讀回 5。正確應回傳 10(T2 寫入主記憶體),實際回傳 5,發生可見性問題。

解決方案

私有副本與主記憶體一致需滿足兩條件之一:

  1. 變數宣告 volatile:每次存取強制與主記憶體同步,語言保證原子性(含 64 位值)。注意:多數 JVM 未正確實作 volatile。
  2. 變數在 synchronized 方法/區塊中存取:進入獲取鎖、退出釋放鎖時同步主記憶體。

效能權衡

  • volatile:每次存取同步,更新頻繁時可能比同步更慢
  • synchronized:僅鎖獲取/釋放時同步,但降低並發性

大量更新且不想每次同步、或需排除並發性時考慮同步。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved