欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java解決線程的不安全問題之volatile關(guān)鍵字詳解

 更新時間:2023年08月26日 09:07:23   作者:一只愛打拳的程序猿  
這篇文章主要介紹了Java解決線程的不安全問題之volatile關(guān)鍵字詳解,可見性指一個線程對共享變量值的修改,能夠及時地被其他線程看到,而 volatile 關(guān)鍵字就保證內(nèi)存的可見性,需要的朋友可以參考下

1. 造成線程不安全的代碼

有一代碼,要求兩個線程運行。

并自定義一個標志位 flag,當線程2(thread2)修改標志位后,線程1(thread1)結(jié)束執(zhí)行。

如下代碼所示:

public class TestDemo3 {
    public static int flag = 0;//自定義一個標志位
    public static void main(String[] args) {
        Thread thread1 = new Thread(()-> {
            while (flag == 0) {
                //空
            }
            System.out.println("thread1線程結(jié)束");
        });//線程1
        Thread thread2 = new Thread(()-> {
            Scanner scanner = new Scanner(System.in);
            System.out.println("請輸入一個整數(shù):");
            flag = scanner.nextInt();
        });//線程2
        thread1.start();//啟動線程1
        thread2.start();//啟動線程2
    }
}

運行后打印:

預(yù)期效果為:thread1 中的 flag==0 作為條件進入 while 循序,thread2 中通過 scanner 輸入一個非 0 的值,從而使得 thread1 線程結(jié)束。

實際效果:thread2 中輸入非 0 數(shù)后,光標處于閃爍狀態(tài)代表循環(huán)未結(jié)束。

造成程序沒有達到如期效果的原因是內(nèi)存的不可見性導(dǎo)致 while 條件判斷始終發(fā)生錯誤。

因此,我們得使用 volatile 關(guān)鍵字來保證內(nèi)存的可見性,使得 while 條件判斷能夠正常識別修改后的標志位 flag。

2. volatile能保證內(nèi)存可見性

可見性指一個線程對共享變量值的修改,能夠及時地被其他線程看到。

而 volatile 關(guān)鍵字就保證內(nèi)存的可見性。

在上述代碼中標志位 flag 未使用 volatile 修飾導(dǎo)致 while 循環(huán)不能正確判斷,其原因如下:

flag == 0這個判斷,會實現(xiàn)兩條操作:

  • 第一條,load 從內(nèi)存讀取數(shù)據(jù)到 cpu的 寄存器。
  • 第二條,cmp 比較寄存器中的值是否為0,是則返回 true 否則返回 false。

但是,編譯器有一個特性:優(yōu)化。優(yōu)化什么呢?

由于進行大量數(shù)據(jù)操作時 load 的開銷很大,編譯器就做出了一個優(yōu)化,就是無論數(shù)據(jù)大或小 load 操作只會執(zhí)行一次。

因此,flag == 0 這個條件第一作為 load 加載到了寄存器中,后序無論對 flag 進行怎樣的修改 cmp 比較的時候始終為 true 了。

這就是多線程運行時,編譯器對于代碼進行優(yōu)化操作的內(nèi)存不可見性。也就是內(nèi)存看不到實際的情況。

因此,我們只需要在 flag 前面加上 volatile 關(guān)鍵字使得編譯器不對 flag 進行優(yōu)化,這樣就能達到效果。如下代碼所示:

public class TestDemo3 {
    volatile public static int flag = 0;//volatile修飾自定義標志位
    public static void main(String[] args) {
        Thread thread1 = new Thread(()-> {
            while (flag == 0) {
                //空
            }
            System.out.println("thread1線程結(jié)束");
        });//線程1
        Thread thread2 = new Thread(()-> {
            Scanner scanner = new Scanner(System.in);
            System.out.println("請輸入一個整數(shù):");
            flag = scanner.nextInt();
        });//線程2
        thread1.start();//啟動線程1
        thread2.start();//啟動線程2
    }
}

運行后打?。?/p>

通過上述代碼及打印結(jié)果,可以看到達到了預(yù)期效果。因此,被 volatile 修飾的變量能夠保證每次從內(nèi)存中重新讀取數(shù)據(jù)。

解釋內(nèi)存可見性:

thread1頻繁讀取主內(nèi)存,效率比較第,就被優(yōu)化成直接讀直接的工作內(nèi)存

thread2修改了主內(nèi)存的結(jié)果,由于thread1沒有讀主內(nèi)存,導(dǎo)致修改不能被識別 

上述的工作內(nèi)存理解為CPU寄存器,主內(nèi)存理解為內(nèi)存。 

3. synchronized與volatile的區(qū)別

3.1 synchronized能保證原子性

以下代碼的需求為:兩個線程分別計算10000 次,使得 count 總數(shù)達到 20000:

//創(chuàng)建一個自定義類
class myThread {
    int count = 0;
    public void run() {
        synchronized (this){
            count++;
        }
    }
    public int getCount() {
        return count;
    }
}
public class TreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        myThread myThread = new myThread();//實例化這個類
        Thread thread1 = new Thread(()-> {
            for (int i = 0; i < 10000; i++) {
                myThread.run();
            }
        });
        Thread thread2 = new Thread(()-> {
            for (int i = 0; i < 10000; i++) {
                myThread.run();
            }
        });
        thread1.start();//啟動線程thread1
        thread2.start();//啟動線程thread2
        thread1.join();//等待線程thread1結(jié)束
        thread2.join();//等待線程thread2結(jié)束
        System.out.println(myThread.getCount());//獲取count值
    }
}

運行后打印:

3.2 volatile不能保證原子性

當我們把上述代碼中的 run 方法去掉 synchronized 的關(guān)鍵字,再給 count 變量加上 volatile 關(guān)鍵字。

//創(chuàng)建一個自定義類
class myThread {
    volatile int count = 0;
    public void run() {
       count++;
    }
    public int getCount() {
        return count;
    }
}

運行后打印:

到此這篇關(guān)于Java解決線程的不安全問題之volatile關(guān)鍵字詳解的文章就介紹到這了,更多相關(guān)Java的volatile關(guān)鍵字內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • spring boot 常見http請求url參數(shù)獲取方法

    spring boot 常見http請求url參數(shù)獲取方法

    這篇文章主要介紹了spring boot 常見http請求url參數(shù)獲取,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03
  • Springboot中的自定義攔截器及原理詳解

    Springboot中的自定義攔截器及原理詳解

    這篇文章主要介紹了Springboot中的自定義攔截器及原理詳解,攔截器主要是用于在用戶請求控制中,對于請求識別,鑒權(quán),以及區(qū)分資源是否可以被目標方法調(diào)用的安全機制,需要的朋友可以參考下
    2023-12-12
  • Java1.8中LocalDate方法使用總結(jié)

    Java1.8中LocalDate方法使用總結(jié)

    LocalDate是Java8中的一個日期類,用于表示年月日,它是不可變的,線程安全的,下面這篇文章主要給大家介紹了關(guān)于Java1.8中LocalDate方法使用的相關(guān)資料,需要的朋友可以參考下
    2024-03-03
  • Java基于對象流實現(xiàn)銀行系統(tǒng)

    Java基于對象流實現(xiàn)銀行系統(tǒng)

    這篇文章主要為大家詳細介紹了Java基于對象流實現(xiàn)銀行系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • 實戰(zhàn)分布式醫(yī)療掛號系統(tǒng)開發(fā)醫(yī)院科室及排班的接口

    實戰(zhàn)分布式醫(yī)療掛號系統(tǒng)開發(fā)醫(yī)院科室及排班的接口

    這篇文章主要為大家介紹了實戰(zhàn)分布式醫(yī)療掛號系統(tǒng)開發(fā)醫(yī)院科室及排班的接口,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪<BR>
    2022-04-04
  • 淺析SpringBoot中的過濾器和攔截器

    淺析SpringBoot中的過濾器和攔截器

    過濾器和攔截器都是為了在請求到達目標處理器(Servlet或Controller)之前或者之后插入自定義的處理邏輯,下面就跟隨小編來看看它們二者的區(qū)別和具體使用吧
    2024-03-03
  • 盤點Java中延時任務(wù)的多種實現(xiàn)方式

    盤點Java中延時任務(wù)的多種實現(xiàn)方式

    當需要一個定時發(fā)布系統(tǒng)通告的功能,如何實現(xiàn)??當支付超時,訂單自動取消,如何實現(xiàn)?其實這些問題本質(zhì)都是延時任務(wù)的實現(xiàn),本文為大家盤點了多種常見的延時任務(wù)實現(xiàn)方法,希望對大家有所幫助
    2022-12-12
  • Java CPU性能分析工具代碼實例

    Java CPU性能分析工具代碼實例

    這篇文章主要介紹了Java CPU性能分析工具代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-01-01
  • 細數(shù)java for循環(huán)中的那些坑

    細數(shù)java for循環(huán)中的那些坑

    這篇文章主要介紹了Java for循環(huán)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-07-07
  • Java如何通過File類方法刪除指定文件夾中的全部文件

    Java如何通過File類方法刪除指定文件夾中的全部文件

    這篇文章主要給大家介紹了關(guān)于Java如何通過File類方法刪除指定文件夾中的全部文件的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-01-01

最新評論