總線鎖(CPU總線):
CPU芯片上有一條引線#HLOCK pin,如果匯編語(yǔ)言的程序中在一條指令前面加上前綴"LOCK",經(jīng)過(guò)匯編以后的機(jī)器代碼就使CPU在執(zhí)行這條指令的時(shí)候把#HLOCK pin的電位拉低,持續(xù)到這條指令結(jié)束時(shí)放開(kāi),從而把總線鎖住,這樣同一總線上別的CPU就暫時(shí)不能通過(guò)總線訪問(wèn)內(nèi)存了,保證了這條指令在多處理器環(huán)境中的原子性.
緩存鎖:
頻繁使用的內(nèi)存會(huì)緩存在處理器的L1,L2和L3高速緩存里,那么原子操作就可以直接在處理器內(nèi)部緩存中進(jìn)行,并不需要聲明總線鎖。
所謂“緩存鎖定”就是如果緩存在處理器緩存行中內(nèi)存區(qū)域在LOCK操作期間被鎖定,當(dāng)它執(zhí)行鎖操作回寫內(nèi)存時(shí),處理器不在總線上聲言LOCK#信號(hào),而是修改內(nèi)部的內(nèi)存地址,并允許它的緩存一致性機(jī)制(當(dāng)某塊CPU對(duì)緩存中的數(shù)據(jù)進(jìn)行操作了之后,就通知其他CPU放棄儲(chǔ)存在它們內(nèi)部的緩存,或者從主內(nèi)存中重新讀取)來(lái)保證操作的原子性.
JAVA原子操作:
在java中可以通過(guò)鎖和循環(huán)CAS(compare and swap)的方式來(lái)實(shí)現(xiàn)原子操作。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class T {
public static void main(String[] args) {
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
threads.add(new Thread(new MyThread()));
}
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
try {
t.join();
} catch (InterruptedException e) {
}
}
System.out.println("counter_i:" + MyThread.counter_i);
System.out.println("counter_volatile:" + MyThread.counter_volatile);
System.out.println("counter_automic:" + MyThread.counter_cas.get());
System.out.println("counter_locker:" + MyThread.counter_locker);
}
}
class MyThread implements Runnable {
static int counter_i;
static volatile int counter_volatile;
static AtomicInteger counter_cas;
static int counter_locker;
static {
counter_cas = new AtomicInteger(0);
counter_i = 0;
counter_volatile = 0;
counter_locker = 0;
}
@Override
public void run() {
// 未保證原子型操作
counter_i++;
// 未保證原子型操作
counter_volatile++;
int i;
do {
// 保證了原子型操作
i = counter_cas.get();
} while (!counter_cas.compareAndSet(i, ++i));
// 保證了原子型操作
synchronized (MyThread.class) {
counter_locker++;
}
}
}
分析:
線程中i++可拆解成如下操作:
a. 從主存復(fù)制變量到線程本地內(nèi)存
b. 讀取線程本地內(nèi)存i
c. 線程本地內(nèi)存i+1
d. 寫回線程本地內(nèi)存
e. 用線程本地內(nèi)存數(shù)據(jù)刷新主存相關(guān)內(nèi)容
直接i++有很多種case會(huì)出現(xiàn)問(wèn)題
分析略
volatile i ++
雖然告訴JVM當(dāng)前變量在寄存器/高速緩存(工作內(nèi)存)中的值是不確定的,需要從主存中讀取), 使修改對(duì)其他線程可見(jiàn),但是仍然沒(méi)有作到i++的原子操作. volatile無(wú)法保證復(fù)合操作的原子性。
AtomicInteger 循環(huán)使用CAS
基于處理器CMPXCHG,保證了原子性. Java的AtomicInteger使用上面提到的系統(tǒng)級(jí)別的總線鎖和緩存鎖來(lái)保證原子操作。
AtomicInteger最終調(diào)用
//object:操作的對(duì)象
//address:操作對(duì)象的屬性地址
//expected:預(yù)期值
//newValue:新值
compareAndSwapInt(Object object, long address, int expected, int newValue)
CAS有幾個(gè)缺點(diǎn):
- ABA:
例如有鏈表 A->B
線程1 compareAndSet(A,B), compare A 之前, 線程2 將鏈表結(jié)構(gòu)變?yōu)锳->C,線程1compare A OK(A地址未變化),并Swap A和 B,鏈表結(jié)構(gòu)變?yōu)锽,和預(yù)期的B->A不同.
解決辦法:使用版本戳,java 中的AtomicStampedReference實(shí)現(xiàn)了該功能。 - 循環(huán)時(shí)間長(zhǎng)
- 只能操作一個(gè)變量
解決辦法:將多個(gè)變量封裝在一個(gè)對(duì)象中,使用AtomicReference<V>。
synchronized 對(duì)i++操作整體加鎖,保證了原子性.
synchronized關(guān)鍵字強(qiáng)制實(shí)施一個(gè)互斥鎖,使得被保護(hù)的代碼塊在同一時(shí)間只能有一個(gè)線程進(jìn)入并執(zhí)行。當(dāng)然synchronized還有另外一個(gè) 方面的作用:在線程進(jìn)入synchronized塊之前,會(huì)把工作存內(nèi)存中的所有內(nèi)容映射到主內(nèi)存上,然后把工作內(nèi)存清空再?gòu)闹鞔鎯?chǔ)器上拷貝最新的值。而 在線程退出synchronized塊時(shí),同樣會(huì)把工作內(nèi)存中的值映射到主內(nèi)存,但此時(shí)并不會(huì)清空工作內(nèi)存。這樣一來(lái)就可以強(qiáng)制其按照上面的順序運(yùn)行,以 保證線程在執(zhí)行完代碼塊后,工作內(nèi)存中的值和主內(nèi)存中的值是一致的,保證了數(shù)據(jù)的一致性!
參考資料:
原子操作的實(shí)現(xiàn)原理
關(guān)于單CPU,多CPU上的原子操作
聊聊并發(fā)(五)——原子操作的實(shí)現(xiàn)原理
CAS原理分析
java 里面保留字volatile及其與synchronized的區(qū)別