參考
《java併發編程的藝術》
《七週七併發模型》
多線程併發編程中最常用的就是synchronized和volatile兩個關鍵字。
volatile
通常被描述成一個輕量級鎖。
用於聲明需要在多線程環境中共享的對象
。 只能在類實例對象上聲明。
功能:
保證當前對象對其它線程可見
禁止代碼的重排序
重排序 :代碼中上下兩段代碼不存在依賴關係時,jvm會對代碼進行優化排序,排序後的順序不一定是代碼的順序
內存屏障 : 一組cpu指令,用於實現對內存操作的順序限制
但是volatile並不能保證線程的安全性
q1:volatile 如何保證可見性
volatile聲明的變量在轉變成彙編語言在底層執行的時候會多出lock的指令。
該指令會引發兩個事情:
- 將當前線程的工作內存中的數據寫回到主內存中
- 寫回內存的操作會讓其它線程工作內存中緩存了該字段的數據無效
Synchronized
通常被描述成重量級鎖,也可稱爲內置鎖
可用於方法,對象,代碼段上
功能 :保證當前操作的線程安全性
保證可見性
底層實現:
synchronized是通過對象的monitor的monitorenter和monitorexit來實現對象鎖的持有和釋放
每個對象上都有一個java 對象頭,用來存放當前對象的鎖信息,hashcode, 分代年齡,是否偏向鎖,鎖標誌位等信息。
鎖按級別從低到高有四種狀態:無鎖,偏向鎖,輕量級鎖,重量級鎖
如圖所示:
缺點:
- 在獲取鎖而進入阻塞狀態時,是無法中斷的
- 嘗試獲取內置鎖時,無法設置超時時間
- 獲得內置鎖,必須使用Synchronized塊
導致的主要問題就是:
死鎖而無法恢復,唯一解決方法是 終止jvm
爲了解決內置鎖的問題,引入reentrantLock(可重入鎖)
可重入鎖 ReentrantLock
優勢:
- 顯示的加鎖和解鎖
Lock lock = new ReentrantLock();
lock.lock();
try{
...
}finally{
lock.unlock()
}
- 可中斷 lock.lockInterruptibly()
- 可設置超時時間 lock.tryLock(1000, TimeUnit.MILLISECONDS)
偏向鎖
是指被同一線程重複獲取的鎖
優點是:
進入和退出鎖的時候不需要cas操作來加鎖,解鎖。效率更快
如何關閉偏向鎖
java1.6, 1.7是默認啓用的,
jvm參數: -XX:BiasedLockingStartupDelay = 0
輕量級鎖的獲取及膨脹流程
鎖的優缺點對比
鎖 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
偏向鎖 | 加鎖和解鎖不需要額外的消耗,和執行非同步方法相比僅存在納米級的差距 | 如果線程之間存在鎖競爭會帶來額外的鎖撤銷的消耗 | 適用於只有一個線程訪問同步塊的情況 |
輕量級鎖 | 競爭的線程不會阻塞,提高線程的響應速度 | 如果始終得不到鎖競爭的線程,使用自旋迴消耗cpu | 追求響應時間,同步塊執行速度非常快 |
重量級鎖 | 線程競爭不使用自旋,不會消耗cpu | 線程阻塞,響應時間緩慢 | 追求吞吐量,同步執行時間較長 |
三者之間的區分,如果只有一個線程重複進入同步塊,那是偏向鎖,如果多個線程進入同步塊,不會阻塞,那是輕量級鎖,會阻塞,是重量級鎖