volatile理解了吗?
到這里大家感覺自己對volatile理解了嗎??
????????如果理解了,大家考慮這么一個問題:ReentrantLock(或者其它基于AQS實現的鎖)是如何保證代碼段中變量(變量主要是指共享變量,存在競爭問題的變量)的可見性?
?
????????既然提到了可見性,那就先熟悉幾個概念:
1、Java Memory Model (JMM)即 Java 內存模型
The Java Memory Model describes what behaviors are legal in multithreaded code, and how threads may interact through memory. It describes the relationship between variables in a program and the low-level details of storing and retrieving them to and from memory or registers in a real computer system. It does this in a way that can be implemented correctly using a wide variety of hardware and a wide variety of compiler optimizations.
????????Java內存模型的主要目標是定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。此處的變量主要是指共享變量,存在競爭問題的變量。Java內存模型規定所有的變量都存儲在主內存中,而每條線程還有自己的工作內存,線程的工作內存中保存了該線程使用到的變量的主內存副本拷貝,線程對變量的所有操作(讀取、賦值等)都必須在工作內存中進行,而不能直接讀寫主內存中的變量(根據Java虛擬機規范的規定,volatile變量依然有共享內存的拷貝,但是由于它特殊的操作順序性規定——從工作內存中讀寫數據前,必須先將主內存中的數據同步到工作內存中,所有看起來如同直接在主內存中讀寫訪問一般,因此這里的描述對于volatile也不例外)。不同線程之間也無法直接訪問對方工作內存中的變量,線程間變量值得傳遞均需要通過主內存來完成。
2、重排序
????????在執行程序時,為了提高性能,編譯器和處理器常常會對指令做重排序。重排序分3種類型:
Parallelism,ILP)來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。
????????從Java源代碼到最終實際執行的指令序列,會分別經歷下面3種重排序:?
??????對于編譯器,JMM的編譯器重排序規則會禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止)。對于處理器重排序,JMM的處理器重排序規則會要求Java編譯器在生成指令序列時,插入特定類型的內存屏障(Memory Barriers,Intel稱之為Memory Fence)指令,通過內存屏障指令來禁止特定類型的處理器重排序。?
??????JMM屬于語言級的內存模型,它確保在不同的編譯器和不同的處理器平臺之上,通過禁止特定類型的編譯器重排序和處理器重排序,為程序員提供一致的內存可見性保證。
3、happens-before
兩個操作之間具有happens-before關系,并不意味著前一個操作必須要在后一個?
操作之前執行!happens-before僅僅要求前一個操作(執行的結果)對后一個操作可見,且前一個操作按順序排在第二個操作之前(the first is visible to and ordered before the second)。
4、內存屏障
- 硬件層的內存屏障分為兩種:Load Barrier 和 Store Barrier即讀屏障和寫屏障。
-
內存屏障有兩個作用:
- 阻止屏障兩側的指令重排序;
- 強制把寫緩沖區/高速緩存中的數據等寫回主內存,讓緩存中相應的數據失效。
-
對于Load Barrier來說,在指令前插入Load Barrier,可以讓高速緩存中的數據失效,強制從新從主內存加載數據;
-
對于Store Barrier來說,在指令后插入Store Barrier,能讓寫入緩存中的最新數據更新寫入主內存,讓其他線程可見。
5、volatile的內存語義
??????從JSR-133開始(即從JDK5開始),volatile變量的寫-讀可以實現線程之間的通信。
??????從內存語義的角度來說,volatile的寫-讀與鎖的釋放-獲取有相同的內存效果:
- volatile寫和鎖的釋放有相同的內存語義;
- volatile讀與鎖的獲取有相同的內存語義。
volatile僅僅保證對單個volatile變量的讀/寫具有原子性,而鎖的互斥執行的特性可以確保對整個臨界區代碼的執行具有原子性。在功能上,鎖比volatile更強大;在可伸縮性和執行性能上,volatile更有優勢。
volatile變量自身具有下列特性:
volatile寫和volatile讀的內存語義
- 線程A寫一個volatile變量,實質上是線程A向接下來將要讀這個volatile變量的某個線程發出了(其對共享變量所做修改的)消息。
- 線程B讀一個volatile變量,實質上是線程B接收了之前某個線程發出的(在寫這個volatile變量之前對共享變量所做修改的)消息。
- 線程A寫一個volatile變量,隨后線程B讀這個volatile變量,這個過程實質上是線程A通過主內存向線程B發送消息。
JMM針對編譯器制定的volatile重排序規則表
- 當第二個操作是volatile寫時,不管第一個操作是什么,都不能重排序。這個規則確保volatile寫之前的操作不會被編譯器重排序到volatile寫之后。
- 當第一個操作是volatile讀時,不管第二個操作是什么,都不能重排序。這個規則確保volatile讀之后的操作不會被編譯器重排序到volatile讀之前。
- 當第一個操作是volatile寫,第二個操作是volatile讀時,不能重排序。
??????為了實現volatile的內存語義,編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序。對于編譯器來說,發現一個最優布置來最小化插入屏障的總數幾乎不可能。為此,JMM采取保守策略。下面是基于保守策略的JMM內存屏障插入策略。
- 在每個volatile寫操作的前面插入一個StoreStore屏障。
- 在每個volatile寫操作的后面插入一個StoreLoad屏障。
- 在每個volatile讀操作的后面插入一個LoadLoad屏障。
- 在每個volatile讀操作的后面插入一個LoadStore屏障。
LoadLoad屏障:對于這樣的語句Load1; www.rbuluoyl.cn/ LoadLoad; Load2,在Load2及后續讀取操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。?
StoreStore屏障:對于這樣的語句Store1; www.thd540.com StoreStore; Store2,在Store2及后續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。?
LoadStore屏障:對于這樣的語句Load1; www.hjd1956.com ?LoadStore; Store2,在Store2及后續寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢。?
StoreLoad屏障:對于這樣的語句Store1; www.yigouyule2.cn StoreLoad; Load2,在Load2及后續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。它的開銷是四種屏障中最大的。在大多數處理器的實現中,這個屏障是個萬能屏障,兼具其它三種內存屏障的功能。
??????上述內存屏障插入策略非常保守,但它可以保證在任意處理器平臺,任意的程序中都能得到正確的volatile內存語義。
??????下面是保守策略下,volatile寫插入內存屏障后生成的指令序列示意圖.?
??????圖中的StoreStore屏障可以保證在volatile寫之前,其前面的所有普通寫操作已經對任意處理器可見了。這是因為StoreStore屏障將保障上面所有的普通寫在volatile寫之前刷新到主內存。
??????這里比較有意思的是,volatile寫后面的StoreLoad屏障。此屏障的作用是避免volatile寫與后面可能有的volatile讀/寫操作重排序。因為編譯器常常無法準確判斷在一個volatile寫的后面是否需要插入一個StoreLoad屏障(比如,一個volatile寫之后方法立即return)。為了保證能正確實現volatile的內存語義,JMM在采取了保守策略:在每個volatile寫的后面,或者在每個volatile讀的前面插入一個StoreLoad屏障。從整體執行效率的角度考慮,JMM最終選擇了在每個volatile寫的后面插入一個StoreLoad屏障。因為volatile寫-讀內存語義的常見使用模式是:一個寫線程寫volatile變量,多個讀線程讀同一個volatile變量。當讀線程的數量大大超過寫線程時,選擇在volatile寫之后插入StoreLoad屏障將帶來可觀的執行效率的提升。從這里可以看到JMM在實現上的一個特點:首先確保正確性,然后再去追求執行效率。
??????下面是在保守策略下,volatile讀插入內存屏障后生成的指令序列示意圖:?
??????圖中的LoadLoad屏障用來禁止處理器把上面的volatile讀與下面的普通讀重排序。LoadStore屏障用來禁止處理器把上面的volatile讀與下面的普通寫重排序。?
??????上述volatile寫和volatile讀的內存屏障插入策略非常保守。在實際執行時,只要不改變volatile寫-讀的內存語義,編譯器可以根據具體情況省略不必要的屏障。
6、AQS
對于AQS需要了解這么幾點:?
1、鎖的狀態通過volatile int state來表示。?
2、獲取不到鎖的線程會進入AQS的隊列等待。?
3、子類需要重寫tryAcquire、tryRelease等方法。
7、ReentrantLock
以公平鎖為例,看看 ReentrantLock 獲取鎖 & 釋放鎖的關鍵代碼:
/*** The synchronization state.*/ private volatile int state; /** * Returns the current value of synchronization state. * This operation has memory semantics of a {@code volatile} read. * @return current state value */ protected final int getState() { return state; } protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c)www.120xh.cn;// 釋放鎖的最后,寫volatile變量state return free; } protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(www.feifanyule.cn);// 獲取鎖的開始,首先讀volatile變量state if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false;??????公平鎖在釋放鎖的最后寫volatile變量state,在獲取鎖時首先讀這個volatile變量。根據volatile的happens-before規則,釋放鎖的線程在寫volatile變量之前可見的共享變量,在獲取鎖的線程讀取同一個volatile變量后將立即變得對獲取鎖的線程可見。從而保證了代碼段中變量(變量主要是指共享變量,存在競爭問題的變量)的可見性。
8、小結
如果我們仔細分析concurrent包的源代碼實現,會發現一個通用化的實現模式。?
首先,聲明共享變量為volatile。?
然后,使用CAS的原子條件更新來實現線程之間的同步。?
同時,配合以volatile的讀/寫和CAS所具有的volatile讀和寫的內存語義來實現線程之間的通信。
前文我們提到過,編譯器不會對volatile讀與volatile讀后面的任意內存操作重排序;編譯器不會對volatile寫與volatile寫前面的任意內存操作重排序。組合這兩個條件,意味著為了同時實現volatile讀和volatile寫的內存語義,編譯器不能對CAS與CAS前面和后面的任意內存操作重排序。
轉載于:https://www.cnblogs.com/qwangxiao/p/9085408.html
總結
以上是生活随笔為你收集整理的volatile理解了吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 量化交易 交易流程与框架
- 下一篇: java虚拟机栈基本内容