天天看點

volatile三大特性

volatile三大特性

1.對volatile的了解

1.volatile是Java虛拟機提供的輕量級的同步機制(同步:synchronized)乞丐版的synchronized。
有三大特性:保證可見性
		  不保證原子性
		  禁止指令重排
           

可見性:

多個線程,通路java主記憶體中的同一對象,擷取對象之後,各自都拷貝到自己的線程記憶體中,當有一個線程中的對象改變時,需要寫回給主記憶體中,主記憶體就會通知其他線程此對象改變了,需要重新拷貝的過程就叫可見性

未加volatile關鍵字
代碼示範
package com.hzy;

import java.util.concurrent.TimeUnit;

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

/**
 * 驗證線程的可見性
 * 1.假如int number = 0 ;number變量之前根本沒有添加volatile關鍵字,沒有可見性
 */
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();
            }
            //修改記憶體中資料為60
            myData.addT060();
            System.out.println(Thread.currentThread().getName()+"\t update number value : "+ myData.number);
        },"AAA").start();

        //第二個線程
        while (myData.number == 0){
            //如果number == 0 mian線程就會在這裡一直循環,知道number的值不在等于0
        }
        //如果number中的資料不再等于0,就會執行到下面代碼
        System.out.println(Thread.currentThread().getName()+"\t mission is over");
    }
}

           

結果:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-7pvxfvLu-1614656364824)(C:\Users\HS\AppData\Roaming\Typora\typora-user-images\image-20210228125951266.png)]

加入volatile關鍵字
代碼示範
package com.hzy;

import java.util.concurrent.TimeUnit;

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

/**
 * 驗證線程的可見性
 * 1.假如int number = 0 ;number變量之前根本沒有添加volatile關鍵字,沒有可見性
 */
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();
            }
            //修改記憶體中資料為60
            myData.addT060();
            System.out.println(Thread.currentThread().getName()+"\t update number value : "+ myData.number);
        },"AAA").start();

        //第二個線程
        while (myData.number == 0){
            //如果number == 0 mian線程就會在這裡一直循環,知道number的值不在等于0
        }
        //如果number中的資料不再等于0,就會執行到下面代碼
        System.out.println(Thread.currentThread().getName()+"\t mission is over,main get number value: " + myData.number);
    }
}

           

結果:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-BaavQ7w6-1614656364827)(C:\Users\HS\AppData\Roaming\Typora\typora-user-images\image-20210228130305136.png)]

不保證原子性:

為什麼沒有保證原子性:

在寫回主記憶體中時,多個線程寫修改了線程内部變量值後,寫回主記憶體,發生了重複寫值現象,沒有區分前後順序,造成了原子性缺失

代碼示範
package com.hzy;

import java.util.concurrent.TimeUnit;

class MyData1{
    volatile int number = 0;

    public void addPlusPlus(){
        number++;
    }
}

/**
 * 驗證線程的不保證原子性
 * 1.原子性:完整性,不可分割,即某個先後才能正在做某個具體業務時,中間不可加塞或者不可分割,需要整體完整
 *      要麼同時成功,要麼同時失敗
 */
public class VolatileDemo2 {
    public static void main(String[] args) {
        MyData1 myData1 = new MyData1();
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {
                    myData1.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }
        //需要等待上面20個線程都全部計算完成後,再用main線程取得最終的結果值
        while(Thread.activeCount() > 2){
            //Thread.yield() 方法,使目前線程由執行狀态,變成為就緒狀态,讓出cpu時間,在下一個線程執行時候,此線程有可能被執行,也有可能沒有被執行。
            Thread.yield();
        }
        //mian線程的最終值
        System.out.println(Thread.currentThread().getName() + "\t finally number value :" + myData1.number);
    }
}


           

如果保持了原子性,最終值應該是20000,實際每次得到的值都不相同

如果需要保證原子性,代碼如下,在計算方法上加sychronized關鍵字

package com.hzy;

import java.util.concurrent.TimeUnit;

class MyData1{
    volatile int number = 0;

    public synchronized void addPlusPlus(){
        number++;
    }
}

/**
 * 驗證線程的不保證原子性
 * 1.原子性:完整性,不可分割,即某個先後才能正在做某個具體業務時,中間不可加塞或者不可分割,需要整體完整
 *      要麼同時成功,要麼同時失敗
 */
public class VolatileDemo2 {
    public static void main(String[] args) {
        MyData1 myData1 = new MyData1();
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {
                    myData1.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }
        //需要等待上面20個線程都全部計算完成後,再用main線程取得最終的結果值
        while(Thread.activeCount() > 2){
            //Thread.yield() 方法,使目前線程由執行狀态,變成為就緒狀态,讓出cpu時間,在下一個線程執行時候,此線程有可能被執行,也有可能沒有被執行。
            Thread.yield();
        }
        //mian線程的最終值
        System.out.println(Thread.currentThread().getName() + "\t finally number value :" + myData1.number);
    }
}

           

結果:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-toL58O0B-1614656364830)(C:\Users\HS\AppData\Roaming\Typora\typora-user-images\image-20210228132625648.png)]

解決不保證原子性問題:

1,在執行運算的方法中加syhronized關鍵字

2.使用JUC下的atomic,

代碼示例:
package com.hzy;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class MyData1 {
    //AtomicInteger 預設值為 0
    AtomicInteger atomicInteger = new AtomicInteger();
    public void addAtomic(){
        //源碼中:加1
        atomicInteger.getAndIncrement();
    }
}
/**
 * 驗證線程的不保證原子性
 * 1.原子性:完整性,不可分割,即某個先後才能正在做某個具體業務時,中間不可加塞或者不可分割,需要整體完整
 *      要麼同時成功,要麼同時失敗
 */
public class VolatileDemo2 {
    public static void main(String[] args) {
        MyData1 myData1 = new MyData1();
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {
                    myData1.addAtomic();
                }
            },String.valueOf(i)).start();
        }
        //需要等待上面20個線程都全部計算完成後,再用main線程取得最終的結果值
        while(Thread.activeCount() > 2){
            //Thread.yield() 方法,使目前線程由執行狀态,變成為就緒狀态,讓出cpu時間,在下一個線程執行時候,此線程有可能被執行,也有可能沒有被執行。
            Thread.yield();
        }
        //mian線程的最終值
        System.out.println(Thread.currentThread().getName() + "\t AtomicInteger finally number value :" + myData1.atomicInteger);
    }
}

           

JMM(java記憶體模型)一種抽象的規範或規則,并不真正存在

可見性,原子性,有序性

禁止指令重排:

在代碼執行時,是編譯為位元組碼指令來提供給程序來執行,在多線程環境下,線程搶到的執行順序可能會有所不同,會造成資料混亂問題,這個時候資料的安全性機會收到威脅,是以就需要禁止指令重排。保證了多線程環境下不會出現亂序執行的現象

使用:在執行的變量前加volatile

禁止指令重排的好處如下,代碼示例:

單線程-單例模式

package com.audition.volatiles;

/**
 * Created with IntelliJ IDEA.
 *
 * @Auther: 兩杯水
 * @Date: 2021/02/28/17:15
 * @Description: 單線程-單例模式
 */
public class VolatileDemo3 {
    private static VolatileDemo3 instance= null;

    private VolatileDemo3(){
        System.out.println(Thread.currentThread().getName() +"\t 我是構造方法 ");
    }

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

    public static void main(String[] args) {
        //main線程的操作動作
        System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());
        System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());
        System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());
    }
}

           

執行結果如下:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-eMHAx9mv-1614656364834)(C:\Users\86178\AppData\Roaming\Typora\typora-user-images\image-20210228172728840.png)]

進行多線程時,就會有問題,代碼如下:

package com.audition.volatiles;

/**
 * Created with IntelliJ IDEA.
 *
 * @Auther: 兩杯水
 * @Date: 2021/02/28/17:15
 * @Description: 單線程-單例模式
 */
public class VolatileDemo3 {
    private static VolatileDemo3 instance= null;

    private VolatileDemo3(){
        System.out.println(Thread.currentThread().getName() +"\t 我是構造方法 ");
    }

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

    public static void main(String[] args) {
       /* //main線程的操作動作
        System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());
        System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());
        System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());*/

        //多線程
        for (int i = 0; i <= 10; i++) {
            new Thread(()->{
                VolatileDemo3.getInstance();
            },String.valueOf(i)).start();

        }
    }
}

           

執行結果如下:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-MhYAi8QI-1614656364836)(C:\Users\86178\AppData\Roaming\Typora\typora-user-images\image-20210228172815095.png)]

得到結果:多線程下的單例模式,由于多個線程執行的時間不同,導緻了構造方法多次被建立執行個體,也就破壞了原來的單例思想

解決方法

1.在執行方法上加synchronized關鍵字:

package com.audition.volatiles;

/**
 * Created with IntelliJ IDEA.
 *
 * @Auther: 兩杯水
 * @Date: 2021/02/28/17:15
 * @Description: 單線程-單例模式
 */
public class VolatileDemo3 {
    private static VolatileDemo3 instance= null;

    private VolatileDemo3(){
        System.out.println(Thread.currentThread().getName() +"\t 我是構造方法 ");
    }

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

    public static void main(String[] args) {
       /* //main線程的操作動作
        System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());
        System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());
        System.out.println(VolatileDemo3.getInstance() == VolatileDemo3.getInstance());*/

        //多線程
        for (int i = 0; i <= 10; i++) {
            new Thread(()->{
                VolatileDemo3.getInstance();
            },String.valueOf(i)).start();

        }
    }
}

           

但是synchronized屬于重量級鎖,此加鎖方式會造成性能降低

2.使用DCL(Double check Lock 雙端檢索機制)

volatile三大特性

但是由于指令重排序的原因,線上程很多并發的情況下,此方法也會導緻對象重複擷取,是以需要對此對象加上volatile

volatile三大特性