前言
在 Java 中,有一個常被忽略但非常重要的關鍵字
synchronized
。
一、關鍵字 synchronized
1. 作用
保證同一時刻最多隻有 1 個線程執行 被
synchronized
修飾的方法 / 代碼
2. 應用場景
保證線程安全,解決多線程中的并發同步問題(實作的是阻塞型并發),具體場景如下:
- 修飾 執行個體方法 / 代碼塊時,(同步)保護的是同一個對象方法的調用 & 目前執行個體對象
- 修飾 靜态方法 / 代碼塊時,(同步)保護的是 靜态方法的調用 & class 類對象
3. 原理
- 依賴 JVM 實作同步
- 底層通過一個螢幕對象(monitor)完成, wait()、notify() 等方法也依賴于 monitor 對象
螢幕鎖(monitor)的本質 依賴于 底層作業系統的互斥鎖(Mutex Lock)實作
4. 使用
synchronized
用于 修飾 代碼塊、類的執行個體方法 & 靜态方法
4.1 鎖的類型 & 等級
由于
synchronized
會修飾 代碼塊、類的執行個體方法 & 靜态方法,故分為不同鎖的類型。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5CO2ADOyYGZyADN4UjY3IjYyYzXyUTNxATMyIzLcZDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
4.2 使用規則
4.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();
}
}
}
}
特别注意:
synchronized
修飾方法時存在缺陷:若修飾 1 個大的方法,将會大大影響效率
-
示例
若使用
關鍵字修飾 線程類的 synchronized
,由于run()
線上程的整個生命期内一直在運作,是以将導緻它對本類任何run()
方法的調用都永遠不會成功synchronized
-
解決方案
使用
關鍵字聲明代碼塊synchronized
該解決方案靈活性高:可針對任意代碼塊 & 任意指定上鎖的對象
代碼如下
synchronized(syncObject) {
// 通路或修改被鎖保護的共享狀态
// 上述方法 必須 獲得對象 syncObject(類執行個體或類)的鎖
}
二、CAS
-
定義
,即 比較并交換,是一種解決并發操作的樂觀鎖Compare And Swap
synchronized 鎖住的代碼塊:同一時刻隻能由一個線程通路,屬于悲觀鎖
- 原理
// 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;
}
-
優點
資源耗費少:相對于
,省去了挂起線程、恢複線程的開銷synchronized
但,若遲遲得不到更新,死循環對CPU資源也是一種浪費
- 具體實作方式
- 使用
有個“先檢查後執行”的操作CAS
- 而這種操作在 Java 中是典型的不安全的操作,是以
在實際中是由CAS
通過調用C++
指令實作的CPU
- 具體過程
- CAS在Java中的展現為Unsafe類
- Unsafe類會通過C++直接擷取到屬性的記憶體位址
- 接下來CAS由C++的Atomic::cmpxchg系列方法實作
-
典型應用:AtomicInteger
對 i++ 與 i–,通過
& 一個死循環實作compareAndSet
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;
}
}