面試被大神虐殘,基礎知識必須重新拾起。閒話少說,進入正題。
多執行緒同時存取同一變數/物件,若同時存在讀寫操作,會導致變數值或物件狀態混亂,引發程式異常。例:銀行帳戶同時被存款與提款執行緒操作,原本餘額 0,若同時發生會出現提款失敗餘額 100 或提款成功餘額 0 等不確定結果。多執行緒同步即解決此問題。
Bank.java (含 addMoney、subMoney、lookMoney) 與 SyncThreadTest.java (兩執行緒分別存/提 100 元)。運行結果顯示餘額混亂、提款邏輯失效。
Java 每個物件有內建鎖,synchronized 修飾方法會保護整個方法,調用前需取得鎖,否則阻塞。修改 Bank.java 兩方法加 synchronized,運行結果:存提交替進行,餘額正確歸零。
註:synchronized 亦可修飾靜態方法,鎖住整個類。
僅鎖住關鍵代碼區塊,減少同步開銷。效果與同步方法相近。註:同步為高開銷操作,應盡量縮小同步範圍。
volatile 提供免鎖存取機制,告知 JVM 該欄位可能被其他執行緒更新,每次從主記憶體讀取而非暫存器。但 不保證原子操作,不可修飾 final 變數。實測加 volatile 後仍出現餘額混亂,因 count+=money 非原子操作。volatile 會阻礙編譯器優化,能不用盡量不用。
Java SE 5.0 java.util.concurrent.locks 提供。ReentrantLock 可重入、互斥、實現 Lock 介面,語義與 synchronized 相似但功能擴展。常用方法:lock()、unlock()。須在 finally 釋放鎖防死鎖。若 synchronized 滿足需求優先使用,需高階功能才用 ReentrantLock。
每個使用該變數的執行緒獲得獨立副本,副本間互不影響。修改 Bank 使用 ThreadLocal<Integer>,運行發現存款執行緒與提款執行緒各自操作自己的副本,提款永遠失敗。ThreadLocal 採「空間換時間」,同步機制採「時間換空間」,各有適用場景。
五種機制各有優劣:synchronized 簡單適用大多數場景;同步代碼塊粒度更細;volatile 適合單一變數可見性但非原子性;ReentrantLock 提供公平鎖、可中斷等高階功能;ThreadLocal 適合執行緒隔離資料,非共享狀態同步。
問啊 - 定制化 IT 教育平台,牛人一對一服務,有問必答,開發編程社交頭條官網:www.wenaaa.com
QQ 群 290551701 歡迎業內人士、大牛及新手加入!