volatile指令重排_有多少人面試栽到Volatile上?面試問題都總結到這兒了

Volatile關鍵字

volatile 是Java虛擬機提供的 輕量級 的同步機制.何為 輕量級 呢,這要相對于 synchronized 來說。Volatile有如下三個特點。

要搞清楚上面列舉的名詞 可見性 原子性 指令重排 的含義我們需要首先弄清楚JMM(Java內存模型是怎么回事)

JMM規定了內存主要劃分為 主內存 和 工作內存 兩種。此處的主內存和工作內存跟JVM內存劃分(堆、棧、方法區)是在不同的層次上進行的,如果非要對應起來,主內存對應的是Java堆中的對象實例部分,工作內存對應的是棧中的部分區域,從更底層的來說,主內存對應的是硬件的物理內存,工作內存對應的是寄存器和高速緩存.

e525f7837536a6327518db463bbf5c24.png

JVM在設計時候考慮到,如果JAVA線程每次讀取和寫入變量都直接操作主內存,對性能影響比較大,所以每條線程擁有各自的工作內存,工作內存中的變量是主內存中的一份 拷貝 ,線程對變量的讀取和寫入,直接在工作內存中操作,而不能直接去操作主內存中的變量。但是這樣就會出現一個問題,當一個線程修改了自己工作內存中變量,對其他線程是不可見的,會導致線程不安全的問題。因為JMM制定了一套標準來保證開發者在編寫多線程程序的時候,能夠控制什么時候內存會被同步給其他線程。

各個線程對主內存中共享變量的操作都是各個線程各自拷貝到自己的工作內存進行操作后再寫回主內存中的。

這就可能存在一個線程A修改了共享變量X的值但還未寫回主內存時,另一個線程B又對準內存中同一個共享變量X進行操作,但此時A線程工作內存中共享變量X對線程B來說并不是可見,這種工作內存與主內存同步存在延遲現象就造成了可見性問題。

通過代碼來看下可見性的問題

package com.dpb.spring.aop.demo;import java.util.concurrent.TimeUnit;/** * 可見性問題分析 */public class VolatileDemo1 {    public static void main(String[] args){        final MyData myData = new MyData();        // 開啟一個新的線程        new Thread(()->{            System.out.println(Thread.currentThread().getName() + "開始了...");            try{TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}            // 在子線程中修改了變量的信息  修改的本線程在工作內存中的數據            myData.addTo60();            System.out.println(Thread.currentThread().getName() + "更新后的數據是:"+myData.number);        },"BBB").start();        // 因為在其他線程中修改的信息主線程的工作內存中的數據并沒有改變所以此時number還是為0        while(myData.number == 0){            // 會一直卡在此處            //System.out.println("1111");        }        System.out.println(Thread.currentThread().getName()+" number =  " + myData.number);    }}class MyData{// 沒有用volatile來修飾    int number = 0;    public void addTo60(){        this.number = 60;    }}

效果如下:

736b682faf5d0463c6d6e9c10dfbf44b.png

通過 volatile 來解決此問題

32b0df9d83917a6f9bb28bc722dbb116.png
5605a338bd345d0655035e47299dd86f.png

我們可以發現當變量被 volatile 修飾的時候,在子線程的工作內存中的變量被修改后其他線程中對應的變量是可以立馬知道的。這就是我們講的可見性

原子性是 不可分割 , 完整性 ,也即某個線程正在做某個具體業務時,中間不可以被加塞或者分割,需要整體完成,要么同時成功,要么同時失敗.

volatile是 不支持 原子性的,接下來我們可以驗證下。

package com.dpb.spring.aop.demo;import java.util.concurrent.TimeUnit;/** * 可見性問題分析 */public class VolatileDemo2 {    public static void main(String[] args){        final MyData2 myData = new MyData2();        for (int i = 1; i <= 20 ; i++) {            new Thread(()->{                for (int j = 1; j <= 1000 ; j++) {                    myData.addPlusPlus();                }            },String.valueOf(i)).start();        }        // 等待子線程執行完成        while(Thread.activeCount() > 2){            Thread.yield();        }        // 在主線程中獲取統計的信息值        System.out.println(Thread.currentThread().getName()+" finnally number value: "+myData.number);    }}class MyData2{   // 操作的變量被volatile修飾了    volatile int number = 0;    public void addPlusPlus(){        number++;    }}

執行的效果

d62bfb39033f4d41c668817bc9bed7e8.gif

根據正常的邏輯在開啟的20個子線程,每個執行1000遍累加,得到的結果應該是20000,但是我們發現運行的結果大概率會比我們期望的要小,而且變量也已經被volatile修飾了。說明并沒有滿足我們要求的原子性。這種情況下我們要保證操作的原子性,我們有兩個選擇

  1. 通過synchronized來實現
  2. 通過 JUC 下的 AtomicInteger 來實現

synchronized的實現是重量級的,影響并發的效率,所以我們通過AtomicInteger來實現。

package com.dpb.spring.aop.demo;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;/** * 可見性問題分析 */public class VolatileDemo2 {    public static void main(String[] args){        final MyData2 myData = new MyData2();        for (int i = 1; i <= 20 ; i++) {            new Thread(()->{                for (int j = 1; j <= 1000 ; j++) {                    myData.addPlusPlus();                    myData.addAtomicPlus();                }            },String.valueOf(i)).start();        }        // 等待子線程執行完成        while(Thread.activeCount() > 2){            Thread.yield();        }        // 在主線程中獲取統計的信息值        System.out.println(Thread.currentThread().getName()+" finnally number value: "+myData.number);        System.out.println(Thread.currentThread().getName()+" finnally number value: "+myData.atomicInteger.get());    }}class MyData2{   // 操作的變量被volatile修飾了    volatile int number = 0;    // AtomicInteger 來保證操作的原子性    AtomicInteger atomicInteger = new AtomicInteger();    public  void addPlusPlus(){        number++;    }    public void addAtomicPlus(){        atomicInteger.getAndIncrement();    }}

效果:

d120d6c98279d098f9cc3ec0933fe182.gif

注意 :通過效果發現 AtomicInteger 在多線程環境下處理的數據和我們期望的結果是一致的都是 20000 .說明實現的操作的原子性。

計算機在執行程序時,為了提高性能,編譯器和處理器常常會對指令做重排,一般分以下3種:

5dd3624036461b7c6256ce895658fb99.png
數據依賴性

案例代碼

package com.dpb.spring.aop.demo;public class SortDemo {    int a = 0;    boolean flag = false;    public void fun1(){        a = 1;  // 語句1        flag = true; // 語句2    }    public void fun2(){        if(flag){            a = a + 5; // 語句3            System.out.println("a = " + a );        }    }}

注意: 在多線程環境中線程交替執行,由于編譯器優化重排的存在,兩個線程中使用的變量能否保證一致性是無法確定的,結果無法預測。

指令重排小結:

volatile實現禁止指令重排優化,從而避免多線程環境下程序出現亂序執行的現象。

先了解一個概念, 內存屏障 又稱 內存柵欄 ,是一個CPU指令,它的作用有兩個:

  1. 是保證特定操作的執行順序
  2. 是保證某些變量的內存可見性(利用該特性實現volatile的內存可見性)

由于編譯器和處理器都能執行指令重排優化。如果在指令間插入一條Memory Barrier則告訴編譯器和CPU,不管什么指令都不能和這條Memory Barrier指令重新排序,也就是說通過插入內存屏障禁止在內存屏障前后的指令執行重排序優化。內存屏障另外一個作用是強制刷出各種CPU的緩存數據,因此任何CPU上的線程都能讀取到這些數據的最新版本。

線程安全的總結:

  1. 工作內存和主內存同步延遲現象導致的 可見性問題 ,可以使用synchronized或volatile關鍵字解決,他們都可以使一個線程修改后的變量立即對其他線程可見。
  2. 對于指令重排導致的 可見性問題 和 有序性問題 ,可以利用volatile關鍵字解決,因為volatile的另外一個作用就是禁止重排序優化。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/272180.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/272180.shtml
英文地址,請注明出處:http://en.pswp.cn/news/272180.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Dubbo核心概念

節點角色規范 節點角色規格Provider提供者公開遠程服務Consumer消費者致電遠程服務Registry注冊表負責服務發現和配置Monitor監視器計算服務調用的數量和耗時Container容器管理服務的生命周期 服務關系 Container負責啟動&#xff0c;加載和運行服務Provider。ProviderRegiste…

良心推薦11款可以稱得上“神器”的Windows工具集合

1、最快文件搜索工具 Everything&#xff1a;當之無愧的最強本地文件搜索神器&#xff0c;搜索任何關鍵詞基本是秒速出現&#xff0c;比Windows自帶的搜索快了太多&#xff0c;電腦文件比較多的人必備&#xff01; 2、專業軟件卸載器 Revo Uninstaller Pro&#xff1a;Windows電…

HDU 2461 Rectangles#容斥原理

http://acm.hdu.edu.cn/showproblem.php?pid2461 題目很簡單&#xff0c;但是由于詢問數M可以很大&#xff0c;所以容易超時&#xff0c;這道題學到了在結構體里面寫函數的方法&#xff0c;這樣子效率更高&#xff0c;否則的話&#xff0c;這道題就TLE了。 根據容斥原理&#…

LD3320語音識別模塊二次開發及與樹莓派間的通訊

實物圖如下&#xff1a; 一般這種模塊的資料廠家都會給&#xff0c;需要的話可以私信我發郵箱&#xff0c;下面介紹該模塊的各種參數。型號&#xff1a;YS-LDV7名稱&#xff1a;一體化語音識別模塊規格&#xff1a;43*29.7MM供電電壓&#xff1a;5V &#xff08;內部工作電壓…

HTTP的長鏈接和短鏈接說明

HTTP的長鏈接和短鏈接實際上是TCP的長連接和短鏈接。首先我們先介紹一下TCP/IP協議組四層模型。其中包括以下&#xff1a; 應用層&#xff1a;HTTP、FTP、DNS、TELNET等協議傳輸層&#xff1a;TCP、UDP網絡層&#xff1a;IP、ARP、RARP、ICMP協議等網絡接口層&#xff1a;是TC…

多生產者_你是生產者還是消費者?這決定了你的層次。

不知道你有沒有注意到&#xff0c;每天乘坐地鐵上下班的時候&#xff0c;大部分人都在刷劇、看視頻、打游戲等等&#xff0c;總之都屬于玩樂。用生產和消費的關系來看的話&#xff0c;其實這一大部分人都屬于消費者&#xff0c;“時間和注意力”是他們用于交換的籌碼&#xff1…

eclipse Android 開發基礎 Activity 窗體 界面

eclipse Android 開發基礎 新建工程 新建布局layout,new Android Activity就相當于窗體Form。 新建Activity自動生成src下同名的java代碼。 public class Tform2activity extends Activity {Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(saved…

8 種常被忽視的 SQL 錯誤用法

來源&#xff1a;http://t.cn/R6UMaA11、LIMIT 語句2、隱式轉換3、關聯更新、刪除4、混合排序5、EXISTS語句6、條件下推7、提前縮小范圍8、中間結果集下推總結sql語句的執行順序&#xff1a;FROM <left_table>ON <join_condition><join_type> JOIN <right…

變頻器按啟動沒反應_起重機軟啟動柜晶閘管損壞維修幾大故障

缺相保護功能&#xff1a;工作時&#xff0c;軟起動器隨時檢測三相線電流的變化&#xff0c;一旦發生斷流&#xff0c;即可作出缺相保護反應。過熱保護功能&#xff1a;通過軟起動器內部熱繼電器檢測晶閘管散熱器的溫度&#xff0c;一旦散熱器溫度超過允許值后自動關斷晶閘管&a…

Redis 的各項功能解決了哪些問題?

作者丨blackheart先看一下Redis是一個什么東西官方簡介解釋到&#xff1a;Redis是一個基于BSD開源的項目&#xff0c;是一個把結構化的數據放在內存中的一個存儲系統&#xff0c;你可以把它作為數據庫&#xff0c;緩存和消息中間件來使用。同時支持strings&#xff0c;lists&am…

python datetime用法_python datetime用法學習筆記

一、主要思路&#xff1a;1.把表示時間的str轉換為datetime對象2.操作datetime對象輸出期望的時間格式二、把表示時間的str轉換為datetime對象語法&#xff1a;datetime.strptime(date_str, format)示例&#xff1a;date_str "2017-06-23 21:08:12"date_obj dateti…

RocketMQ集成SpringBoot

RocketMQ集成SpringBoot RocketMQ總體架構 RocketMQ基本特性

協議(Protocol)與委托代理(Delegate)

協議(Protocol)的作用&#xff1a; 1. 規范接口&#xff0c;用來定義一套公用的接口&#xff1b; 2. 約束或篩選對象。 代理(Delegate)&#xff1a; 它本身是一種設計模式&#xff0c;委托一個對象<遵守協議>去做某件事情&#xff0c;目的是為了降低對象間的耦合度&#…

ASP.NET Core 2.2+Quartz.Net 實現Web定時任務

作者&#xff1a;Julian_醬鏈接&#xff1a;http://www.cnblogs.com/mi12205599/p/10361763.html作為一枚后端程序狗&#xff0c;項目實踐常遇到定時任務的工作&#xff0c;最容易想到的的思路就是利用Windows計劃任務/wndows service程序/Crontab程序等主機方法在主機上部署定…

lj245a引腳功能圖_ULN2003A引腳圖及功能-uln2003a原理

ULN是集成達林頓管IC&#xff0c;內部還集成了一個消線圈反電動勢的二極管&#xff0c;可用來驅動繼電器。它是雙列16腳封裝,NPN晶體管矩陣,最大驅動電壓50V,電流500mA,輸入電壓5V,適用于TTL COMS,由達林頓管組成驅動電路。ULN是集成達林頓管IC,內部還集成了一個消線圈反電動勢…

RocketMQ核心概念

生產者Producer和消費者Consumer NameServer作用 Broker和Topic

交叉編譯、軟硬鏈接

什么是交叉編譯&#xff1f;交叉編譯是一個行為&#xff0c;是在一個平臺上生成另一個平臺上的可執行代碼。 本地編譯&#xff1a;本地編譯可以理解為&#xff0c;在當前編譯平臺下&#xff0c;編譯出來的程序只能放到當前平臺下運行。平時我們常見的軟件開發&#xff0c;都是…

掃地機器人狗毛_掃地機器人:我是清理狗毛的!不是清理狗屎的!

原標題&#xff1a;掃地機器人&#xff1a;我是清理狗毛的&#xff01;不是清理狗屎的&#xff01;掃地機器人可以清潔地面和角落里的垃圾&#xff0c;對于滿是毛毛的鏟屎官家庭來說&#xff0c;簡直就是福音吶&#xff01;不過最近&#xff0c;槽點卻有點多&#xff1a;家里買…

Linus下安裝maven

下載maven安裝包 wget http://mirror.bit.edu.cn/apache/maven/binaries/apache-maven-3.2.2-bin.tar.gz 解壓 tar -zxvf apache-maven-3.2.2-bin.tar.gz 配置maven環境變量 查看maven解壓后安裝包目錄 vi /etc/profile 進入最底部&#xff0c;按insert,添加環境變量&#x…