volatile關鍵字複習

volatile關鍵字是Java虛擬機提供的輕量級的同步機制
它具有以下三個特性:
1)保證可見性
2)無法確保原子性
3)禁止指令重排序
線程操作變量的工作流程

在這裏插入圖片描述
1.普通情況下,在多線程環境下,一個線程在完成對變量拷貝副本的修改等操作後,其他線程是無法立即得知的,可能會導致死循環問題。但當該變量被volatile關鍵字修飾後,某一線程修改了這個變量的值,其他變量會立即得知該變量的值已改變,這就是volatile能保證可見性的體現。
驗證代碼:

class MyData{
    volatile int number = 0;
  
    public void AddTo60(){
        this.number = 60;
    }
}

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

         new Thread(()->{
            System.out.println(Thread.currentThread().getName() +"\t come in");

            try{
                TimeUnit.SECONDS.sleep(3);
            }catch (InterruptedException e){
                e.printStackTrace();
            }

            mydata.AddTo60();

            System.out.println(Thread.currentThread().getName()+"\t updated number value->"+mydata.number);
         },"AAA").start();


        //第2個線程就是我們的main線程
        //number未加volatile修飾時,此時main線程並不知道主內存中的number值已經等於60,就會一直在這裏死循環等待
         while(mydata.number == 0){
             //main線程就一直在這裏等待循環,直到number不等於0
         }

         System.out.println(Thread.currentThread().getName() +"\t int type,此時number的值爲:"+mydata.number);
 }
}

未加volatile關鍵字修飾時,運行結果:
在這裏插入圖片描述上述代碼運行,運行結果:
在這裏插入圖片描述
2.原子性指的是不可分割,完整性,也即某個線程正在做某個具體的業務時,中間不可以被加塞或者分割,需要整體完整, 要麼同時成功,要麼同時失敗。
使用volatile關鍵字修飾的變量在多線程高併發的情況下,同樣是線程不安全。因爲在java中運算並不是原子性操作。原因如圖,
在這裏插入圖片描述驗證一下,貼代碼:

class MyData{
    volatile int number = 0;
    public void AddPlusPlus()
    {
        //此時number已經被volatile關鍵字修飾
        number++;
    }
}

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

        for (int i = 0; i <20 ; i++) {

            new Thread(()->{
                for (int j = 1; j <=1000 ; j++) {
                    data.AddPlusPlus();
                    data.myaddAtomic();
                }
            },String.valueOf(i)).start();

        }

        //需要等待上面20個線程全部計算完成後,再利用main線程取得最終的結果值
        while(Thread.activeCount() >2){
            Thread.yield();
        }

        //結果並不等於20000,證明volatile關鍵字不保證原子性
        //可能導致寫丟失的情況
        System.out.println(Thread.currentThread().getName()+"\t Int Type,final number:"+data.number);
    }
}

運行結果:在這裏插入圖片描述值不等於20000,且每次運行結果都不一樣。

使用AtomicInteger類,

class MyData{
    AtomicInteger atomicInteger = new AtomicInteger();

    public void myaddAtomic()
    {
        atomicInteger.getAndIncrement();
    }
}

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

        for (int i = 0; i <20 ; i++) {

            new Thread(()->{
                for (int j = 1; j <=1000 ; j++) {
                    data.AddPlusPlus();
                    data.myaddAtomic();
                }
            },String.valueOf(i)).start();

        }

        //需要等待上面20個線程全部計算完成後,再利用main線程取得最終的結果值
        while(Thread.activeCount() >2){
            Thread.yield();
        }

       
       System.out.println(Thread.currentThread().getName() +"\t AtomicInteger Type,final number,此時number的值爲:"
                +data.atomicInteger);

    }
}

運行結果,在這裏插入圖片描述
實際上,++操作在底層編譯階段是進行了三個步驟!
所以,在多線程的情況下,可能會出現丟失寫回主內存的值的情況。
爲了避免這種情況出現,我們可以使用sychronized關鍵字修飾方法或者使用AtomicInteger類(推薦使用的)。

3.禁止重排序。什麼是重排序?
重排序是指令的重排序。爲了提高性能,編譯器和處理器常常會對指令做重排序,重排序就會導致多線程執行的時候有數據不一致問題,導致程序結果不是理想結果。

使用volatile關鍵字修飾後,就不會發生重排序。

在這裏插入圖片描述

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