## 提綱

## 定義
`synchronized`是同步塊,實現了多線程間的互斥同步。它修飾的代碼,確保任一時刻只有一個線程進入訪問。
## 特性
因為在`synchronized`同步塊內,只有一個線程能訪問,因此確保了同步塊內的原子性、可見性和有序性。
## 使用方式

總結:
```
Class tClass = T.class; // T.class其實就是該類的類對象
```
***synchronized不管是修飾代碼塊還是修飾方法,本質都是作用于對象上。進入代碼塊時需要獲取對象鎖,退出同步塊是釋放對象鎖。***
## synchronized底層實現原理

Java對象鎖的信息存在Java對象頭里的mark word中。
synchronized不管是修飾代碼塊還是修飾方法,都能確定一個對象與之關聯監視器。
對象監視器(ObjectMonitor)是在jdk中使用c++實現的,具體細節需閱讀對應源碼。
## synchronized vs ReentrantLock

總結:
1. 在`>=JDK1.6`后,`jvm`對`synchronized`關鍵字的鎖做了很多優化,其性能和`ReentrantLock`的Api式鎖相差無幾;不過新的api的鎖支持3個高級特性。
2. `ReentrantLock`的底層實現是基于`AQS`的;`synchronized`是`jvm`基于字節碼`monitorenter`和`monitorexit`加上一些鎖優化實現的。
## 提高鎖性能
### 減少鎖持有時間

### 減小鎖粒度
在`JDK1.7`中`ConcurrentHashMap`實用了分段鎖來減小鎖粒度(縮小鎖對象的范圍),從而降低鎖沖突的可能性,進而提高系統的并發能力。
### 讀寫分離替換獨占鎖
在讀多寫少的場合使用讀寫鎖可以有效提升系統的并發能力。
### 鎖分離
鎖分離是讀寫鎖的進一步延伸,讀寫鎖是根據讀寫操作上的不同,對鎖進行了有效的分離。
在其他角度的分離思想,也可以對獨占鎖進行分離。
比如`LinkedBlockingQueue`的實現,其中`take()`和`put()`分別實現了從隊列中獲取數據和往隊列中增加數據的功能,將獨占鎖分離為頭鎖和尾鎖能提升`take()`和`put()`的并發能力。

## 鎖優化
在`<=JDK1.5`時,`synchronized`直接就是重量級鎖,所以性能不好。在`JDK1.6`版本中,平臺對這部分的鎖性能做了很多優化,例如鎖消除、鎖粗化、偏向鎖、自適應自旋、輕量級鎖等優化。
### 鎖消除

低于JDK1.5版本,編譯器會將+號連接字符串的代碼優化為StringBuffer的連續append()操作;然后即時編譯器會對代碼做“逃逸分析”發現sb不會超出方法外,因此會將append方法內的同步完全消除掉執行,提高效率。
### 鎖粗化

虛擬機在遇到一連串連續地對同一個鎖不斷進行請求和釋放的操作時,便會把所有的鎖操作整合成對鎖的一次請求,從而減少對鎖的請求同步次數,這個操作叫做鎖粗化。
### 偏向鎖
優化思想:
如果一個線程獲得了鎖(通過CAS將當前線程指針記錄到mark word中),那么鎖就進入偏向模式,當該線程再次請求鎖時,不需要做任何同步操作。
適用場景:
對于沒有任何鎖競爭的場合,偏向鎖優化效果好。
***在鎖競爭激烈的場景,如果每次來請求鎖的線程都是不同線程,那么偏向模式會失效。***
JVM配置參數:
-XX:+UseBiasedLocking 開啟偏向鎖優化。
-XX:BiasedLockingStartupDelay=4 偏向鎖延遲啟動,默認4秒 。
### 輕量級鎖
如果偏向鎖失敗,虛擬機會嘗試輕量級鎖的優化手段。
優化思想:
***對于絕大部分的鎖,在整個同步周期內都是不存在競爭的。(這是一個經驗數據)***
如果沒有競爭,輕量級鎖使用CAS操作避免使用互斥量的重量級鎖開銷。
單如果有競爭,CAS和互斥量開銷都有,因此在有競爭的情況下,輕量級鎖比重量級鎖更慢。
實現:
在同步對象沒有被鎖定(鎖標志位為01狀態),虛擬機會在當前線程的棧中建立鎖記錄(Lock Record)的空間,然后通過CAS將對象的Mark Word對應位存儲為鎖記錄的指針。如果成功,則說明獲取輕量級鎖成功,并更新該對象的Mark Word的鎖標志位為00。
如果有2條以上線程爭用同一個鎖,則輕量級鎖失效,會執行鎖升級過程。
### 自旋&自適應自旋
如果輕量級鎖失敗,虛擬機還會做最后的嘗試(自旋的優化)。
優化思想:
當前線程暫時無法獲得鎖,也許在幾個CPU時鐘周期后就可以獲得鎖。因此先不掛起線程,而是讓線程做幾個空循環后,如果獲取到鎖則進入臨界區(還是輕量級鎖狀態);如果還是沒有獲取到鎖,就膨脹為重量級鎖。
JVM配置參數:
-XX:+UseSpinning 開啟自旋鎖,JDK1.4.2已經引入,默認關閉。在JDK1.6之后默認開啟。
-XX:PreBlockSpin=10 自旋次數,默認10次。后面加入自適應自旋后該參數無效。
自適應自旋:
手動設置自旋次數其實是不合理的,所以程序會根據前一次在同一個鎖上的自旋時間及鎖的擁有者狀態來決定。
* 如果上一次剛剛成功通過自旋獲取過鎖,且持有鎖的線程正在運行中,虛擬機會認為這次自旋也很有可能成功,進而允許更長時間的自旋等待。
* 如果對于某個鎖,自旋很少成功過,則虛擬機會省略自旋獲取鎖的過程,避免浪費處理器資源。
## 鎖升級

詳細流程如下圖:

例子:
~~~
/**
* 鎖升級測試 jdk版本=1.8
* -XX:+UseBiasedLocking 默認1.6之后就開啟了偏向鎖
* -XX:BiasedLockingStartupDelay=5 偏向鎖啟動延遲,單位秒,系統默認值是4
*
* 結論:
* 當不開啟偏向鎖時,能得到 001(無鎖) -> 000(輕量級鎖) -> 010(重量級鎖)
* 開啟偏向鎖時,并設置延時5秒,new之后sleep 6秒,for循環內1個線程,則能得到 001(無鎖) -> 000(輕量級鎖) -> 101(偏向鎖)
* 開啟偏向鎖時,并設置延時5秒,new之后sleep 6秒,for循環內2個線程及以上,則能得到 001(無鎖) -> 000(輕量級鎖) -> 010(重量級鎖)
*/
public class LockUpTest {
// 鎖對象
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// new狀態 -- 001
System.out.println(Thread.currentThread().getName() + " -- " + ClassLayout.parseInstance(lock).toPrintable());
Thread.sleep(6000);
synchronized (lock){
// 輕量級鎖 -- 000
System.out.println(Thread.currentThread().getName() + " -- " + ClassLayout.parseInstance(lock).toPrintable());
}
// 偏向鎖 -- 101
Object newLock = new Object();
new Thread(()->{
synchronized (newLock) {
System.out.println(Thread.currentThread().getName() + " -- " + ClassLayout.parseInstance(newLock).toPrintable());
}
}).start();
// 重量級鎖 -- 010(當線程數大于1)
for(int i=0;i<2;i++){
new Thread(()->{
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " -- " + ClassLayout.parseInstance(lock).toPrintable());
}
}).start();
}
}
}
~~~
輸出:

## 參考資料
* 書籍 周志明 * 《深入理解Java虛擬機》
* 書籍 葛一鳴 * 《Java高并發程序設計》
* 網上文章 - https://www.cnblogs.com/Alei777/p/16223842.html
- 面試突擊
- Java虛擬機
- 認識字節碼
- 000Java發展歷史
- 000Macos10.15.7上編譯OpenJDK8u
- 001熟悉Java內存區域
- 002熟悉HotSpot中的對象
- 003Java如何計算對象大小
- 004垃圾判定算法與4大引用
- 005回收堆和方法區中對象
- 006垃圾收集算法
- 007HotSpot虛擬機垃圾算法實現篇1
- 007HotSpot虛擬機垃圾算法實現篇2
- 007HotSpot虛擬機垃圾算法實現篇3
- 008垃圾收集器
- 009內存分配與回收策略
- 010Java虛擬機相關工具
- 011調優案例分析
- 012一次IDEA的啟動速度調優
- 013類文件Class的結構
- 014熟悉字節碼指令
- 015類加載機制(過程)
- 016類加載器
- IDEA的JVM參數
- Java基礎
- Java自動裝箱與拆箱
- Java基礎數據類型
- Java方法的參數傳遞
- Java并發
- 001走入并行的世界
- 002并行程序基礎
- 003熟悉Java內存模型JMM
- 004Java并發之volatile關鍵字
- 005線程池入門到精通
- 006Java多線程間的同步控制方法
- 007Java維基準測試框架JMH
- 008Java并發容器
- 009Java的線程實現
- 010Java關鍵字synchronized
- 011一些并行模式的熟悉
- 單例模式和不變模式
- 生產者消費者模式
- Future模式
- 012一些并行算法的熟悉
- 面試總結
- 長亮一面