Java面試之Volatile關鍵字(詳解)
你好我是辰兮很高興你能來閱讀,本篇給你分析Java面試常考的Volatile 關鍵字,分享獲取新知,希望對你有幫助,大家一起進步!
文章目錄
一、序言
二、volatile概念簡介
三、JMM以及共享變量的可見性
四、并發編程的基本概念
五、鎖的互斥和可見性
六、volatile變量的特性
七、Volatile原理
一、序言
面試常會遇到Volatile 關鍵字的相關問題
Volatile修飾符其實很早就存在于C和C++中
在了解Volatile關鍵字的時候,我們要了解Java的內存模型
Java內存模型簡稱JMM(Java Memory Model),是Java虛擬機所定義的?種抽象規范,?來屏蔽不同硬件和操作系統的內存訪問差異,讓java程序在各種平臺下都能達到?致的內存訪問效果。
ps:初學者很容易吧Java內存模型和Java內存結構搞混,注意這是兩個不同的概念。
1.主內存(Main Memory)
主內存可以簡單理解為計算機當中的內存,但?不完全等同。主內存被所有的線程所共享,對于?個共享變量(?如靜態變量,或是堆內存中的實例)來說,主內存當中存儲了它的“本尊”。
2.工作內存(Working Memory)
工作內存可以簡單理解為計算機當中的CPU?速緩存,但又不完全等同。每?個線程擁有自己的工作內存,對于?個共享變量來說,工作內存當中存儲了它的“副本”。
線程對共享變量的所有操作都必須在工作內存進行,不能直接讀寫主內存中的變量。不同線程之間也無法訪問彼此的工作內存,變量值的傳遞只能通過主內存來進行。
你要知道如果全部在主內存中完成,操作效率會很低。
感興趣的參考我的另外一篇文章:硬盤,內存和CPU的關系
二、volatile概念簡介
1、volatile是Java提供的一種輕量級的同步機制。
2、
Java 語言包含兩種內在的同步機制
:同步塊(或方法)和 volatile 變量,相比synchronized(synchronized通常稱為重量級鎖),volatile更輕量級,因為它不會引起線程上下文的切換和調度。但是volatile 變量的同步性較差(有時它更簡單并且開銷更低),而且其使用也更容易出錯。
3、volatile關鍵字具有許多特性,其中最重要的特性就是
保證了用volatile修飾的變量對所有線程的可見性。
小結:volatile是一種同步機制,相對synchronized更加輕量級,保證了可見性
拓展補充:這里的可見性是什么意思呢?
當一個線程修改了變量的值,新的值會立刻同步到主內存當中。而其他線程讀取這個變量的時候,也會從主內存中拉取最新的變量值。
三、JMM以及共享變量的可見性
1、JMM決定一個線程對共享變量的寫入何時對另一個線程可見
2、JMM定義了線程和主內存之間的抽象關系:共享變量存儲在主內存(Main Memory)中,每個線程都有一個私有的本地內存(Local Memory),本地內存保存了被該線程使用到的主內存的副本拷貝,線程對變量的所有操作都必須在工作內存中進行,而不能直接讀寫主內存中的變量。
小結:Java內存模型決定了共享變量的可見性,每一個線程操作數據都是在自己的內本地內存中操作,不能直接在主內存中操作。
想了解Java內存模型請參考:Java內存模型詳解
四、并發編程的基本概念
(1)原子性
定義: 即一個操作或者多個操作 要么全部執行并且執行的過程不會被任何因素打斷,要么就都不執行。
原子性是拒絕多線程操作的,不論是多核還是單核,具有原子性的量,同一時刻只能有一個線程來對它進行操作。
簡而言之,在整個操作過程中不會被線程調度器中斷的操作,都可認為是原子性。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。
Java中的原子性操作包括:
a. 基本類型的讀取和賦值操作,且賦值必須是數字賦值給變量,變量之間的相互賦值不是原子性操作。
b.所有引用reference的賦值操作
c.java.concurrent.Atomic.* 包中所有類的一切操作
(2)可見性
定義:指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
在多線程環境下,一個線程對共享變量的操作對其他線程是不可見的。Java提供了volatile來保證可見性,當一個變量被volatile修飾后,表示著線程本地內存無效,當一個線程修改共享變量后他會立即被更新到主內存中,其他線程讀取共享變量時,會直接從主內存中讀取。
當然,synchronize和Lock都可以保證可見性。synchronized和Lock能保證同一時刻只有一個線程獲取鎖然后執行同步代碼,并且在釋放鎖之前會將對變量的修改刷新到主存當中。因此可以保證可見性。
(3)有序性
定義:即程序執行的順序按照代碼的先后順序執行。
Java內存模型中的有序性可以總結為:如果在本線程內觀察,所有操作都是有序的;如果在一個線程中觀察另一個線程,所有操作都是無序的。前半句是指“線程內表現為串行語義”,后半句是指“指令重排序”現象和“工作內存主主內存同步延遲”現象。
在Java內存模型中,為了效率是允許編譯器和處理器對指令進行重排序,當然重排序不會影響單線程的運行結果,但是對多線程會有影響。
Java提供volatile來保證一定的有序性。最著名的例子就是單例模式里面的DCL(雙重檢查鎖)。
另外,可以通過synchronized和Lock來保證有序性,synchronized和Lock保證每個時刻是有一個線程執行同步代碼,相當于是讓線程順序執行同步代碼,自然就保證了有序性。
五、鎖的互斥和可見性
鎖提供了兩種主要特性:互斥(mutual exclusion) 和可見性(visibility)。
(1)互斥即一次只允許一個線程持有某個特定的鎖,一次就只有一個線程能夠使用該共享數據。
(2)可見性要更加復雜一些,它必須確保釋放鎖之前對共享數據做出的更改對于隨后獲得該鎖的另一個線程是可見的。也即當一條線程修改了共享變量的值,新值對于其他線程來說是可以立即得知的。如果沒有同步機制提供的這種可見性保證,線程看到的共享變量可能是修改前的值或不一致的值,這將引發許多嚴重問題。
要使 volatile 變量提供理想的線程安全,必須同時滿足下面兩個條件:
a.對變量的寫操作不依賴于當前值。
b.該變量沒有包含在具有其他變量的不變式中。
實際上,這些條件表明,可以被寫入 volatile 變量的這些有效值獨立于任何程序的狀態,包括變量的當前狀態。事實上就是保證操作是原子性操作,才能保證使用volatile關鍵字的程序在并發時能夠正確執行。
六、volatile變量的特性
(1)保證可見性,不保證原子性
a.當寫一個volatile變量時,JMM會把該線程本地內存中的變量強制刷新到主內存中去;
b.這個寫會操作會導致其他線程中的緩存無效。
(2)禁止指令重排
重排序:是指編譯器和處理器為了優化程序性能而對指令序列進行排序的一種手段。
重排序需要遵守一定規則:
a.重排序操作不會對存在數據依賴關系的操作進行重排序。
比如:a=1;b=a; 這個指令序列,由于第二個操作依賴于第一個操作,所以在編譯時和處理器運行時這兩個操作不會被重排序。
b.重排序是為了優化性能,但是不管怎么重排序,單線程下程序的執行結果不能被改變。
解釋一下重排序
編譯器或者CPU的代碼的結構重排排序,達到最佳效果。
1、編譯器重排(案例)
//優化前 int X=1; inty=2; int a1=x*1; int b1 = y*1; inta2=X*2; intb2=y*2; //優化后 intX=1; inty=2; int a1=x*1; inta2=x*2; intb1=y*1; intb2=y*2;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CPU只讀一次的x和y值。不需反復讀取寄存器來交替x和y值。
七、Volatile原理
volatile可以保證線程可見性且提供了一定的有序性,但是無法保證原子性。在JVM底層volatile是采用“內存屏障”來實現的。
什么是內存屏障?
內存屏障,也稱內存柵欄,內存柵障,屏障指令等, 是一類同步屏障指令
是CPU或編譯器在對內存隨機訪問的操作中的一個同步點,使得此點之前的所有讀寫操作都執行后才可以開始執行此點之后的操作。
觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的匯編代碼發現,加入volatile關鍵字時,會多出一個lock前綴指令,lock前綴指令實際上相當于一個內存屏障(也成內存柵欄),內存屏障會提供3個功能:
① 它確保指令重排序時不會把其后面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的后面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成;
②它會強制將對緩存的修改操作立即寫入主存;
③如果是寫操作,它會導致其他CPU中對應的緩存行無效。
The best investment is to invest in yourself
2020.07.07 記錄辰兮的第87篇博客
Java 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。