volatile可見性禁重排原理以及不保證原子性解決方案,volatile下的雙端檢鎖機制單例模式的原理

先說說併發和並行的區別
1.併發是多個線程同時訪問
2.並行是多個方面一起正在做

volatile是Java虛擬機提供的輕量級同步機制

三大特性:保證可見性,不保證原子性,禁止指令重排
JMM(java內存模型)
高併發系統還是單機版系統(高併發伴隨很多問題,不得不研究底層JMM)
在這裏插入圖片描述
在這裏插入圖片描述
JMM第一大特性之內存可見性
變量值一旦被某個線程優先修改改變 其他線程立刻可見
在這裏插入圖片描述對象在堆裏面,整個虛擬機在內存裏面
JMM三大特性
在這裏插入圖片描述
可見性舉例

package com.wsx;

import java.util.concurrent.TimeUnit;

class add{

    volatile int i = 1;

    public void addTo20(){
        this.i = 20;
    }
}

public class volatileDemo {
    public static void main(String[] args) {

        final add add = new add();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+'\t'+add.i);//之前的值
            try {
                TimeUnit.SECONDS.sleep(2);//睡兩秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            add.addTo20();//將值設置爲20

        },"AAA").start();

        while (add.i==1){
            //若不同步值則一直在此循環
        }

        //主線程值
        System.out.println(Thread.currentThread().getName()+'\t'+add.i);
    }
}

在這裏插入圖片描述
在這裏插入圖片描述
volatile不保證原子性的案例演示

/**
 * volatile不保證原子性
 */
class Number{
    volatile int number = 0;

    public void numberPlus(){
        number++;
    }
}
public class VolatileNoAtomic {
    public static void main(String[] args) {
        Number number = new Number();

        for (int i = 1; i <= 20; i++) {//二十個線程
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {//一個線程打印2000次
                    number.numberPlus();
                }
            },String.valueOf(i)).start();
        }

        while (Thread.activeCount()>2){//activeCount是因爲main一個線程gc一個線程然後如果這二十個線程組打印完則會小於2,故停止對main線程的休眠
            Thread.yield();//Thread是main線程
        }

        System.out.println(Thread.currentThread().getName()+"number加後的值:"+number.number);
    }
}

volatile不保證原子性理論性解釋
number++底層是三個步驟
三個步驟(先拿number,然後++,然後再 放入主內存)
剛好寫完又特別快,兩個掛起的線程(此時已經累加過,但是沒有被可見性同步,因爲掛起)又被喚醒,沒有拿到最新值,再一次去寫(寫時被掛起,也沒有接到通知(可見性)),然後就出現了值覆蓋現象
不保證原子性就可能出現寫丟失的情況,線程太快了,後面線程會把前面的寫的值覆蓋
在這裏插入圖片描述
字節碼指令集:https://www.cnblogs.com/taomylife/p/8340605.html

我拿的時候,我們兩個拿的都是0,我正準備寫回去是1的時候,突然有人捷足先登了,我這兒被掛起,已經有人從0先寫去1,而這個時候,我又馬上寫回去,那麼相當於跟丟了一次
在這裏插入圖片描述volatile關鍵字不保證原子性問題解決
在這裏插入圖片描述原子操作,天生單位最小不可分割

package com.wsx;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * volatile不保證原子性
 * 解決方案:
 * 1.加sync關鍵字
 * 2.使用atomicInteger
 */
class MyData{

    volatile int i = 0;

    public void numberPlusPlus(){
        i++;
    }

    AtomicInteger atomicInteger = new AtomicInteger();

    public void numberPlus(){
        atomicInteger.getAndIncrement();//相當於i++,此構造函數默認無參構造器是從0開始累加,可以傳入int初始值
    }
}

public class VolatileSolveNoAtomic {
    public static void main(String[] args) {
        MyData myData = new MyData();

        for (int i = 1; i <= 20; i++) {//二十個線程
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {//一個線程打印2000次
                    myData.numberPlusPlus();
                    myData.numberPlus();
                }
            },String.valueOf(i)).start();
        }

        while (Thread.activeCount()>2){//activeCount是因爲main一個線程gc一個線程然後如果這二十個線程組打印完則會小於2,故停止對main線程的休眠
            Thread.yield();//Thread是main線程
        }

        System.out.println(Thread.currentThread().getName()+"number加後的值:"+myData.i);
        System.out.println(Thread.currentThread().getName()+"number加後的值:"+myData.atomicInteger);
    }
}

線程安全性獲得保證:可見性,原子性,有序性

volatile指令重排案例1
高考時,做題,先找會的做,做題順序不一定和出題順序一致,這就是指令重排
在這裏插入圖片描述
編譯器,系統重排,內存重排
在這裏插入圖片描述不可以,理由是數據依賴性在這裏插入圖片描述禁止指令重排案例2在這裏插入圖片描述有可能是5也有可能是6
因爲a=1和flag=true沒有依賴性,可能先flag=true先執行然後a=a+5馬上跟着執行然後就成a=0+5
使用volatile關鍵字可以解決指令重排在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述線程安全性獲得保證在這裏插入圖片描述
單例模式在多線程環境下可能存在的安全性問題
使用volatile寫單例模式
單機版單例模式

package com.wsx;

public class SingletonDemo {

    private static SingletonDemo instance = null;

    private SingletonDemo(){
        System.out.println(Thread.currentThread().getName()+"\t"+"我是單例模式的構造方法");
    }

    public static SingletonDemo getInstance(){
        if(instance == null){
            instance = new SingletonDemo();
        }
        return instance;
    }

    public static void main(String[] args) {

        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());

    }
}

多線程版單例模式

package com.wsx;

public class SingletonDemo {

    private static SingletonDemo instance = null;

    private SingletonDemo(){
        System.out.println(Thread.currentThread().getName()+"\t"+"我是單例模式的構造方法");
    }

    public static SingletonDemo getInstance(){//此處加sync可以解決,但是鎖了整個方法
        if(instance == null) {
            instance = new SingletonDemo();
        }
        return instance;
    }

    public static void main(String[] args) {

//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                SingletonDemo.getInstance();
            },String.valueOf(i)).start();
        }

    }
}

單例模式volatile分析
DCL(double Check Lock雙端檢鎖機制)
demo1
這種情況很小几率不安全,因爲存在指令重排情況

package com.wsx;

public class SingletonThreadDemo {

    private static SingletonThreadDemo instance = null;

    private SingletonThreadDemo(){
        System.out.println(Thread.currentThread().getName()+"\t"+"我是單例模式的構造方法");
    }

    public static SingletonThreadDemo getInstance(){//此處加sync可以解決,但是鎖了整個方法
        //雙端檢鎖機制
        if(instance == null) {

            synchronized (SingletonDemo.class){

                if(instance == null){

                    instance = new SingletonThreadDemo();

                }
            }

        }
        return instance;
    }

    public static void main(String[] args) {

//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                SingletonThreadDemo.getInstance();
            },String.valueOf(i)).start();
        }

    }
}

在這裏插入圖片描述在這裏插入圖片描述高併發下DCL的volatile的單例模式
最終版本

package com.wsx;

public class SingletonThreadDemo {

    //加volatile防止指令重排
    private static volatile SingletonThreadDemo instance = null;

    private SingletonThreadDemo(){
        System.out.println(Thread.currentThread().getName()+"\t"+"我是單例模式的構造方法");
    }

    public static SingletonThreadDemo getInstance(){//此處加sync可以解決,但是鎖了整個方法
        //雙端檢鎖機制
        if(instance == null) {

            synchronized (SingletonThreadDemo.class){

                if(instance == null){

                    instance = new SingletonThreadDemo();

                }
            }

        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                SingletonThreadDemo.getInstance();
            },String.valueOf(i)).start();
        }

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