多線程筆記3——共享模型之內存

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對象

由於是底層,不能直接獲得,需要通過反射獲得

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章