Java併發的核心就是 j.u.c 包,而 j.u.c 的核心是AbstractQueuedSynchronizer
抽象隊列同步器,簡稱 AQS,一些鎖啊!信號量啊!循環屏障啊!都是基於AQS。而 AQS 又是基於Unsafe
的一系列compareAndSwap
,所以理解了這塊,併發不再是問題!
- 先解釋下何爲
compareAndSwap
就拿AtomicInteger
先開刀:
// 實際操作的值
private volatile int value;
// value 的偏移量 因爲 int 是32位,知道首部地址就可以了
private static final long valueOffset;
// 靜態初始化塊,通過虛擬機提供的接口,獲得 valueOffset
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 只是個封裝方法,起作用的代碼並不在這
// 值得注意的是顯示的 this 和第三個參數 1
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// 以下是 Unsafe 類 可以直接訪問內存地址,類似指針,所以不安全
// o getAndIncrement()傳入的 this,也就是 AtomicInteger 實例對象
// offset 內存首部偏移量
// delta 就是那個 1
// 應該是希臘字母 δ /'deltə/ delta 變化量,化學反應中的加熱,屈光度,一元二次方程中的判別式
// 佩服
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
// 從堆內存獲取最新的 value
// 如果不明白,可以先了解下 JMM 和 volatile
public native int getIntVolatile(Object o, long offset);
// expected 就是這個 v = getIntVolatile(o, offset);
// 意思就是,我給你這個最新的 value,它要是現在 在內存中還是這個值 那你就返回 true,並且把這塊內存上值更新爲 x
// 不然的話,我就一直 while (!compareAndSwapInt(o, offset, v, v + delta));
// 相當於自旋鎖 活鎖,不要被高大上的術語嚇到 就是活的循環,不會像死鎖那樣線程 hang 住
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
- 第二刀就拿
ReentrantLock
開刀好了,這幾個類中的同步,由於方法和功能不一,細節處理上可能不一樣。但是,原理都是一樣的,離不開上述的CAS
// The synchronization state. 官方註釋
// 我簡單解釋一下,synchronized 原理是給加鎖的對象 加上一個 monitorenter 和 monitorexit 的指令
// 當某個線程進入加鎖的代碼(實際上應該是拿到被加鎖的對象在內存的引用地址),會執行 monitorenter 然後將 monitor 置爲1,當別的線程訪問該內存時,發現 monitor 不爲 0
// 所以其它線程無法獲得 monitor,直到佔有 monitor 的線程執行 monitorexit 將 monitor 減 1
// 如果佔有 monitor 的線程 重複進入,monitor 是可以一直累加的
// 瞭解了 synchronized 工作原理,就會明白爲什麼會有諸如 nonfairTryAcquire(1) release(1) 的方法
// 這是 AbstractQueuedSynchronizer 類中的字段
// 因爲 ReentrantLock 中的內部類 Sync 繼承於 AQS
private volatile int state;
// tryLock why ?
// 因爲不同於 synchronized 的悲觀(我纔不管你是不是併發,多線程,聲明瞭,我就加鎖)
// 所以 ReentrantLock 我先 try 一 try 吧!萬一不是多線程併發呢!🤣
public boolean tryLock() {
// 加鎖 加 1
return sync.nonfairTryAcquire(1);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 0 的話 說明沒有線程佔有
// 可以獲得鎖
if (c == 0) {
// 這個和上面的 AtomicInteger 一樣
if (compareAndSetState(0, acquires)) {
// 設置當前佔有鎖的線程
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
// 看見沒,鎖的計數有可能會有問題
// 因爲一直累計,指不定就加到 int 上限轉負數了
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 不爲 0 更新 state
setState(nextc);
return true;
}
return false;
}
// AbstractOwnableSynchronizer 類
// The current owner of exclusive mode synchronization.
// 排它鎖 獨佔鎖 寫鎖 都一個意思 鎖的當前持有線程
private transient Thread exclusiveOwnerThread;
總結一下:
Doug Lea 真正的大師,從他的代碼中可以看出對於細節的處理與把控,以及對於我等代碼閱讀者的友好
大道至簡,誰能想到 Java 的併發支持是基於一些加 1 減 1 的運算