1. JAVA內存模型 JMM
JMM體現在在以下幾方面:
原子性:指令不會受到線程上下文切換的影響
可見性:保證指令不會受CPU緩存的影響
有序性:指令不會受CPU指令並行優化的影響
1.1 可見性
因爲t要頻繁的從主存中讀取run的值,JIT即時編譯器會將run的值緩存到自己的高速緩存中,減少主存對run的訪問,提高效率。
解決方法:
(1)volatile關鍵字:修飾成員變量和靜態成員變量,可以強制從主存總讀取。適用於一個線程寫,剩下線程讀。不保證原子性
(2)synchronized關鍵字:既能保證原子性也能保證可見性,但是成本高
1.2有序性
JVM會對指令進行指令重排來提高效率:在不改變結果的前提下,指令通過重排序和組合實現指令級並行
解決方法:volatile
例子:double-checked locking 懶漢單例模式
public final class Singleton{
private static volatile Singleton INSTANCE = null;
private Singleton(){}
public static Singleton getInstance(){
if(INSTANCE == null){
synchronized(Singleton.class){
if(INSTANCE == null)
INSTANCE = new Singleton();
}
}
return INSTANCE;
}
}
如果不加volatile,可能會引起指令重排:線程1進入同步代碼塊剛剛執行完new Singleton(),對應的字節碼爲下面四條
指令21(調用構造方法new Singleton(),申請空間)和24(賦值,把構造好的那塊地址賦值給INSTANCE)可能會重排。
1.3 volatile原理
底層實現原理是內存屏障, Memory Barrier
對volatile讀指令前加入讀屏障,對volatile寫指令後會加入寫屏障。讀寫屏障保證有序性和可見性
eg.
保證有序性:寫屏障之前的代碼不會被重排到寫屏障後,讀屏障之後的代碼不會重排到屏障之前
保證可見性:num=2和 ready = true會先同步到主存中,而if(ready)之後的代碼都會從主存中讀取。
2.CAS
compare and swap 無鎖併發
原子整數:AtomicInteger,
原子引用:AtomicReference(存在ABA問題),AtomicStampedReference, AtomicMarkableReference
原子數組:
原子累加器:LongAdder(效率更高,原理等效於MAPREDUCE)
LongAdder原理:緩存一致性,防止僞共享
3. Unsafe對象
由於是底層,不能直接獲得,需要通過反射獲得