Java:手把手教你全面學習神祕的Synchronized關鍵字

前言
- 在
Java
中,有一個常被忽略 但 非常重要的關鍵字Synchronized
- 今天,我將詳細講解
Java
關鍵字Synchronized
的所有知識,希望你們會喜歡
目錄

示意圖
1. 定義
Java
中的1個關鍵字
2. 作用
保證同一時刻最多隻有1個執行緒執行 被 Synchronized
修飾的方法 / 程式碼
其他執行緒 必須等待當前執行緒執行完該方法 / 程式碼塊後才能執行該方法 / 程式碼塊
3. 應用場景
保證執行緒安全,解決多執行緒中的併發同步問題(實現的是阻塞型併發),具體場景如下:
- 修飾 例項方法 / 程式碼塊時,(同步)保護的是同一個物件方法的呼叫 & 當前例項物件
- 修飾 靜態方法 / 程式碼塊時,(同步)保護的是 靜態方法的呼叫 & class 類物件
4. 原理
- 依賴
JVM
實現同步 - 底層通過一個監視器物件
(monitor)
完成,wait()
、notify()
等方法也依賴於 monitor 物件
監視器鎖(monitor)的本質 依賴於 底層作業系統的互斥鎖(Mutex Lock)實現
5. 具體使用
Synchronized
用於 修飾 程式碼塊、類的例項方法 & 靜態方法
5.1 使用規則

示意圖
5.2 鎖的型別 & 等級
Synchronized

示意圖
- 之間的區別

示意圖
5.3 使用方式
/** * 物件鎖 */ public class Test{ // 物件鎖:形式1(方法鎖) public synchronized void Method1(){ System.out.println("我是物件鎖也是方法鎖"); try{ Thread.sleep(500); } catch (InterruptedException e){ e.printStackTrace(); } } // 物件鎖:形式2(程式碼塊形式) public void Method2(){ synchronized (this){ System.out.println("我是物件鎖"); try{ Thread.sleep(500); } catch (InterruptedException e){ e.printStackTrace(); } } } } /** * 方法鎖(即物件鎖中的形式1) */ public synchronized void Method1(){ System.out.println("我是物件鎖也是方法鎖"); try{ Thread.sleep(500); } catch (InterruptedException e){ e.printStackTrace(); } } /** * 類鎖 */ public class Test{ // 類鎖:形式1 :鎖靜態方法 public static synchronized void Method1(){ System.out.println("我是類鎖一號"); try{ Thread.sleep(500); } catch (InterruptedException e){ e.printStackTrace(); } } // 類鎖:形式2 :鎖靜態程式碼塊 public void Method2(){ synchronized (Test.class){ System.out.println("我是類鎖二號"); try{ Thread.sleep(500); } catch (InterruptedException e){ e.printStackTrace(); } } } }
5.4 特別注意
Synchronized
修飾方法時存在缺陷:若修飾1個大的方法,將會大大影響效率
-
示例
若使用
Synchronized
關鍵字修飾 執行緒類的run()
,由於run()
線上程的整個生命期內一直在執行,因此將導致它對本類任何Synchronized
方法的呼叫都永遠不會成功 -
解決方案
使用
Synchronized
關鍵字宣告程式碼塊
該解決方案靈活性高:可針對任意程式碼塊 & 任意指定上鎖的物件
程式碼如下 synchronized(syncObject) { // 訪問或修改被鎖保護的共享狀態 // 上述方法 必須 獲得物件 syncObject(類例項或類)的鎖 }
6. 特點

示意圖
注:原子性、可見性、有序性的定義

示意圖
7. 其他控制併發 / 執行緒同步方式
7.1 Lock、ReentrantLock
- 簡介

示意圖
- 區別

示意圖
7.2 CAS
7.2.1 定義
Compare And Swap
,即 比較 並 交換,是一種解決併發操作的樂觀鎖
synchronized
鎖住的程式碼塊:同一時刻只能由一個執行緒訪問,屬於悲觀鎖
7.2.2 原理
// CAS的操作引數 記憶體位置(A) 預期原值(B) 預期新值(C) // 使用CAS解決併發的原理: // 1. 首先比較A、B,若相等,則更新A中的值為C、返回True;若不相等,則返回false; // 2. 通過死迴圈,以不斷嘗試嘗試更新的方式實現併發 // 虛擬碼如下 public boolean compareAndSwap(long memoryA, int oldB, int newC){ if(memoryA.get() == oldB){ memoryA.set(newC); return true; } return false; }
7.2.3 優點
資源耗費少:相對於 synchronized
,省去了掛起執行緒、恢復執行緒的開銷
但,若遲遲得不到更新,死迴圈對 CPU
資源也是一種浪費
7.2.4 具體實現方式
- 使用CAS有個“先檢查後執行”的操作
- 而這種操作在Java中是典型的不安全的操作,所以
CAS
在實際中是 由C++
通過呼叫CPU指令實現的 - 具體過程
// 1. CAS在Java中的體現為Unsafe類 // 2. Unsafe類會通過C++直接獲取到屬性的記憶體地址 // 3. 接下來CAS由C++的Atomic::cmpxchg系列方法實現
7.2.5 典型應用: AtomicInteger
對 i++ 與 i--,通過 compareAndSet
& 一個死迴圈實現
而 compareAndSet
函式內部 = 通過 jni
操作 CAS
指令。直到CAS操作成功跳出迴圈
private volatile int value; /** * Gets the current value. * * @return the current value */ public final int get() { return value; } /** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } /** * Atomically decrements by one the current value. * * @return the previous value */ public final int getAndDecrement() { for (;;) { int current = get(); int next = current - 1; if (compareAndSet(current, next)) return current; } }
8. 總結
-
本文主要對
Java
中常被忽略 但 非常重要的關鍵字Synchronized
進行講解 -
下面我將繼續對
Android & Java
中的知識進行深入講解 ,有興趣可以繼續關注 Carson_Ho的安卓開發筆記
請點贊!因為你的鼓勵是我寫作的最大動力!
歡迎關注Carson_Ho的簡書!
不定期分享關於 安卓開發 的乾貨,追求 短、平、快 ,但 卻不缺深度 。
