天天看點

Votatile--JMM--單例模式

​​volatile和synchronized的差別​​​​全面了解Java記憶體模型​​

Votatile

文章目錄

  • ​​Votatile​​
  • ​​JMM​​
  • ​​Votatile​​
  • ​​什麼是JMM​​
  • ​​Volatile​​
  • ​​保證了可見性​​
  • ​​不保證原子性​​
  • ​​避免指令重排​​
  • ​​指令重排​​
  • ​​volatile可以避免指令重排​​
  • ​​單例模式​​
  • ​​餓漢式​​
  • ​​DCL懶漢式​​
  • ​​DCL懶漢式​​
  • ​​反射破環唯一性​​
  • ​​解決: 繼續加鎖​​
  • ​​但是雙反射繼續破環​​
  • ​​繼續解決: 采用紅略燈經行.加标志進行判定​​
  • ​​破環: 利用反射破環其私有權限​​
  • ​​枚舉​​
  • ​​通過讀取源碼得值,反射不能破環枚舉,利用枚舉​​
  • ​​利用枚舉​​
  • ​​注: 枚舉的最終反編譯源碼​​

JMM

Votatile

Votatile是java虛拟機輕量級的同步機制

  1. 保證了可見性
  2. 不保證原子性
  3. 禁止指令重排

什麼是JMM

JMM:java記憶體模型,不存在的東西,概念!!約定!!

關于JMM的一些同步的約定

  1. 線程解鎖前,必須把共享變量立即刷回主記憶體.
  2. 線性加鎖前,必須讀取主記憶體中的最新值到工作記憶體中
  3. 加鎖和解鎖是同一把鎖

線程 工作記憶體 、主記憶體

Votatile--JMM--單例模式
Votatile--JMM--單例模式
問題:程式不知道主記憶體的值已經被修改過了

記憶體互動操作有8種,虛拟機實作必須保證每一個操作都是原子的,不可在分的(對于double和long類型的變量來說,load、store、read和write操作在某些平台上允許例外)

  1. lock(鎖定):作用于主記憶體的變量,把一個變量辨別為線程獨占狀态
  2. unlock(解鎖):作用于主記憶體的變量,它把一個處于鎖定狀态的變量釋放出來,釋放後的變量才可以被其他線程鎖定
  3. read(讀取):作用于主記憶體變量,它把一個變量的值從主記憶體傳輸到線程的工作記憶體中,以便随後的load動作使用
  4. load(載入):作用于工作記憶體的變量,它把read操作從主存中變量放入工作記憶體中
  5. use(使用):作用于工作記憶體中的變量,它把工作記憶體中的變量傳輸給執行引擎,每當虛拟機遇到一個需要使用到變量的值,就會使用到這個指令
  6. assign(指派):作用于工作記憶體中的變量,它把一個從執行引擎中接受到的值放入工作記憶體的變量副本中
  7. store(存儲):作用于主記憶體中的變量,它把一個從工作記憶體中一個變量的值傳送到主記憶體中,以便後續的write使用
  8. write(寫入):作用于主記憶體中的變量,它把store操作從工作記憶體中得到的變量的值放入主内

存的變量中JMM對這八種指令的使用,制定了如下規則:

  1. 不允許read和load、store和write操作之一單獨出現。即使用了read必須load,使用了store必須write
  2. 不允許線程丢棄他最近的assign操作,即工作變量的資料改變了之後,必須告知主存
  3. 不允許一個線程将沒有assign的資料從工作記憶體同步回主記憶體
  4. 一個新的變量必須在主記憶體中誕生,不允許工作記憶體直接使用一個未被初始化的變量。就是怼變量實施use、store操作之前,必須經過assign和load操作
  5. 一個變量同一時間隻有一個線程能對其進行lock。多次lock後,必須執行相同次數的unlock才能解鎖
  6. 如果對一個變量進行lock操作,會清空所有工作記憶體中此變量的值,在執行引擎使用這個變量前,必須重新load或assign操作初始化變量的值
  7. 如果一個變量沒有被lock,就不能對其進行unlock操作。也不能unlock一個被其他線程鎖住的變量
  8. 對一個變量進行unlock操作之前,必須把此變量同步回主記憶體
問題:程式不知道主記憶體的值已經被修改過了

Volatile

  1. 保證了可見性
  2. 不保證原子性
  3. 禁止指令重排

保證了可見性

public class JMMDemo {

    //不加volatile 程式就會死循環
    //加volatile 保證了可見性
    private  volatile static  int num=0;

    public static void main(String[] args) {
        new Thread(()->{
            while (num==0){

            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num=1;
        System.out.println(num);
    }
}      

不保證原子性

原子性: 不可分割

線程A在執行任務的時候,不能被打擾的,也不能被分割,要麼成功,要麼失敗

避免指令重排

指令重排

你寫的程式,計算機并不是按照你寫的那樣去執行的。

源代碼–>編譯器優化的重排–> 指令并行也可能會重排–>記憶體系統也會重排–> 執行

volatile可以避免指令重排

記憶體屏障.CPU指令,

  • 保證特定的操作的執行順序
  • 可以保證某些變量的記憶體可見性記憶體屏障:禁止上面指令和下面指令順序交換

Volatile是可以保持可見性。不能保證原子性,由于記憶體屏障,可以保證避免指令重排的現象産生!

單例模式

餓漢式DCL懶漢式

餓漢式

// 餓漢式單例
public class hun {

    // 可能會浪費空間
    private byte[] data1 = new byte[1024 * 1024];
    private byte[] data2 = new byte[1024 * 1024];
    private byte[] data3 = new byte[1024 * 1024];
    private byte[] data4 = new byte[1024 * 1024];

    private hun() {

    }

    private final static hun HUNGRY = new hun();

    public static hun getInstance() {
        return HUNGRY;
    }

}
      

DCL懶漢式

DCL懶漢式

Votatile--JMM--單例模式

反射破環唯一性

單例不安全,反射可以進行破環

這裡的雙重檢測加鎖是保證了操作原子性,隻有一個線程能建立一個執行個體,其他線程無法建立第二個,volatie關鍵字是為了防止因為指令重排導緻的多線程問題,有可能A 建立了一個執行個體,虛拟機隻配置設定空間,對象引用這兩步,這既是線程B過來發現對象已經被建立了,但是擷取到的對象還是沒有被初始化

if (lazyMan == null) {

lazyMan = new LazyMan(); //不是原子性操作

/**

* 由于不是原子

*

* 1.配置設定記憶體空間

* 2.執行構造方法,初始化對象

* 3.把這個對象指向這個空間

*會出現指令重排

*

* 加上 volatile 不會出現指令重排

*/

}

Votatile--JMM--單例模式

解決: 繼續加鎖

Votatile--JMM--單例模式

但是雙反射繼續破環

Votatile--JMM--單例模式

繼續解決: 采用紅略燈經行.加标志進行判定

Votatile--JMM--單例模式

破環: 利用反射破環其私有權限

Votatile--JMM--單例模式

枚舉

通過讀取源碼得值,反射不能破環枚舉,利用枚舉

Votatile--JMM--單例模式

利用枚舉

枚舉有參構造

注: 枚舉的最終反編譯源碼