java加鎖
A. Synchronized原理
synchronized是Java中用於加鎖的關鍵字,它允許為對象和方法,以及代碼塊加鎖。當synchronized用於鎖定一個方法或代碼塊時,同一時刻最多隻有一個線程能夠執行這段代碼。如果另一個線程試圖訪問加鎖的代碼塊,它必須等待當前線程執行完該代碼塊後才能執行。然而,當一個線程訪問加鎖的對象的代碼塊時,另一個線程仍可以訪問該對象的非加鎖代碼塊。
synchronized有三種主要的應用方式:
修飾實例方法:這會在當前實例上加鎖,確保在執行同步代碼前獲取實例鎖。
修飾靜態方法:這會在當前類的對象(即Class對象)上加鎖,進入同步代碼前要獲取Class對象的鎖。
修飾代碼塊:這允許指定鎖定的對象,對給定對象加鎖,確保在進入同步代碼塊前獲取對象的鎖。
Java對象頭包括兩部分:Mark Word 和 Class Metadata Address,用於存儲對象的運行時數據,如哈希碼、GC分代年齡、鎖狀態標志等。在32位虛擬機中,Mark Word的32個Bits中用於存儲對象的哈希碼、分代年齡、鎖標志等信息。
Monitor對象在Java中是一個同步工具,它負責管理對象的鎖狀態。每個Java對象都關聯著一個Monitor,它在對象創建時生成,用於控制線程對對象的訪問。Monitor由ObjectMonitor實現,包含兩個隊列:_WaitSet 和 _EntryList,分別用於保存等待鎖的線程列表和持有鎖的線程列表。當線程試圖獲取鎖時,會進入 _EntryList;獲取鎖後,線程進入 _Owner 區域並設置Monitor的owner變數。當線程調用wait()方法時,它會釋放Monitor鎖,讓出機會給其他線程。
Synchronized關鍵字在位元組碼層面通過monitorenter和monitorexit指令實現同步代碼塊和同步方法的加鎖與解鎖。在方法調用時,JVM會檢查方法是否為同步方法,如果是,則線程會先獲取Monitor鎖,然後執行方法。當方法執行完畢時,無論以何種方式結束,都會釋放Monitor鎖。Java SE1.6引入了偏向鎖、輕量級鎖和重量級鎖來減少鎖操作的開銷。偏向鎖和輕量級鎖是樂觀鎖,它們在多個線程競爭時才升級為重量級鎖,後者依賴於操作系統互斥量實現,開銷較大。
自旋鎖是一種在獲取鎖失敗時,讓線程在當前CPU上進行循環等待的優化策略,以避免線程切換的開銷。自適應自旋鎖則根據前一次在同一個鎖上的自旋情況,調整自旋次數,以提高效率。
鎖消除是JVM在逃逸分析的基礎上,識別出不存在共享數據競爭的代碼塊,從而移除不必要的同步鎖,以節省資源。
鎖的膨脹流程大致為:偏向鎖 -> 輕量級鎖 -> 重量級鎖。偏向鎖是為單線程優化的,輕量級鎖在多線程競爭不激烈時使用,當競爭加劇時升級為重量級鎖。重量級鎖使用操作系統互斥量實現,成本較高。自旋鎖和自適應自旋鎖則是在獲取鎖失敗時,通過在當前CPU上循環等待,減少線程切換的開銷。