国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

volatile 和 synchronized

2019-11-08 02:58:25
字體:
來源:轉載
供稿:網友

volatile 與可見性

可見性

volatile 的語義之一,意思是一個線程修改了共享變量時,另一個線程能夠讀到這個修改后的值。

舉例說明

每個線程都擁有獨有的本地內存,而 JMM 控制著主內存和本地內存的數據交換。

public class VolatileExample01 { public static void main(String[] args) throws Exception { MyThread thread = new MyThread(); thread.start(); try { Thread.sleep(3000); } finally { thread.setFlag(true); } }}class MyThread extends Thread { PRivate boolean flag = false; @Override public void run() { while (!flag) { //System.out.println("Running..."); } } public void setFlag(boolean flag) { this.flag = flag; }}

上述代碼運行起來有可能形成死循環。


過程分析: 上述代碼中主線程將 flag 讀取到本地內存進行修改,然后刷新到主內存;然而線程 thread 一直在讀取其本地內存中的 flag 值,無法看到主線程對 flag 變量做的修改,因此造成死循環。

解決辦法: 用 volatile 修飾共享變量

volatile private boolean flag = false

結論: volatile 修飾共享變量能夠保證其的可見性。

volatile 實現可見性的原理

原理:【附錄】volatile 的內存語義。

volatile 禁止指令重排序

此為 volatile 的語義之一。 查看:重排序 原理:【附錄】volatile 的內存語義。

volatile 并不能保證變量同步

public class SynchronizedExample01 { public static void main(String[] args) throws Exception { Mythread02[] threadArray = new Mythread02[100]; for (int i = 0; i < 100; i++) { threadArray[i] = new Mythread02(); } for (int i = 0; i < 100; i++) { threadArray[i].start(); } Thread.sleep(10000); System.out.println(Mythread02.getCount()); }}class Mythread02 extends Thread{ volatile private static int count = 0; @Override public void run() { for (int i = 0; i < 100; i++) { add(); } } public void add(){ count++; } public static int getCount(){ return count; }}

如上代碼運行三次,輸出的結果分別為:

9998、9941、9895

都沒有達到理想的結果 10000;


原因分析: 此處 count++ 不具有原子性。

結論: volatile 不具有原子性,不能保證變量同步; 要保證變量同步還得用 synchronized 關鍵字。

synchronized 實現變量同步

修改上述代碼,去掉 volatile 關鍵字,使用 synchronized 將 add() 同步,如下:

public class SynchronizedExample01 { public static void main(String[] args) throws Exception { Mythread02[] threadArray = new Mythread02[100]; for (int i = 0; i < 100; i++) { threadArray[i] = new Mythread02(); } for (int i = 0; i < 100; i++) { threadArray[i].start(); } Thread.sleep(10000); System.out.println(Mythread02.getCount()); }}class Mythread02 extends Thread{ private static int count = 0; @Override public void run() { for (int i = 0; i < 100; i++) { add(); } } synchronized public void add(){ count++; } public static int getCount(){ return count; }}

運行輸出的結果還是會小于 10000。


分析: synchronized 加在非 static 方法上意思是鎖住當前對象,而多個線程使用的是多個對象,一個線程進來后出來之前,另一個線程還是會進來的。

對策: 將 add() 改為靜態方法,保證所有線程在這個方法上使用的是同一個對象鎖。

synchronized static public void add(){ count++;}

結論: synchronized 能夠保證變量的同步性;但是一定要注意,同步方法或同步塊一定要使用同一個對象鎖。

synchronized 與可見性

舉例說明

我們回到文章開頭的死循環的例子; 除了使用 volatile 修飾共享變量能保證可見性之外,synchronized 同樣可以實現。 我們可以這樣改:

public class VolatileExample01 { public static void main(String[] args) throws Exception { MyThread thread = new MyThread(); thread.start(); try { Thread.sleep(3000); } finally { thread.setFlag(true); } }}class MyThread extends Thread { private boolean flag = false; @Override public void run() { while (!flag) { synchronized (this) { // do something ... } } } public void setFlag(boolean flag) { this.flag = flag; }}

分析:

這里寫圖片描述

結論: synchronized 也是可以實現變量對所有線程可見的。

synchronized 實現可見性的原理

原理:【附錄】鎖的內存語義。

volatile 和 synchronized 對比總結

這里寫圖片描述

【附錄】使用 synchronized 實現同步的表現形式

當用在普通方法的時候,鎖是當前的實例對象;當用在靜態方法的時候,鎖是當前類的 Class 對象;當用在代碼塊的時候,鎖是括號中配置的對象;

【附錄】volatile 的內存語義

volatile 的內存語義

volatile 的讀內存語義:當讀一個 volatile 變量時,JMM 將該線程對應的本地內存置為無效,從主內存中讀取變量; volatile 的寫內存語義:當寫一個 volatile 變量時,JMM 將該線程對應的本地內存中的共享變量值刷新到主內存。

volatile 內存語義實現原理

什么是內存屏障:用于實現對內存操作的順序限制。

這里寫圖片描述


為了實現 volatile 的內存語義,JMM 會限制重排序:

限制 volatile 修飾的共享變量之間的重排序;限制 volatile 修飾的共享變量與普通共享變量之間的重排序;

下面是 JMM 制定的 volatile 重排序規則表:

這里寫圖片描述

JMM 通過插入內存屏障的方式限制重排序,如下圖:

這里寫圖片描述

這里寫圖片描述

詳解:

在每個 volatile 寫操作前插入 StoreStore 屏障在每個 volatile 寫操作后插入 StoreLoad 屏障在每個 volatile 讀操作后插入 LoadLoad 屏障在每個 volatile 讀操作后插入 LoadStore 屏障

【附錄】鎖的內存語義

鎖的內存語義

線程釋放鎖前,JMM 會將共享變量的最新值刷新到主內存中;線程獲取鎖時,JMM 會將線程對應的本地內存置為無效,從而在需要共享變量的時候必須去主內存中讀取,同時保存在本地內存。

注意:加鎖解鎖必須是同一把鎖。

可以看出,鎖釋放和 volatile 讀具有相同的內存語義;鎖獲取和 volatile 寫具有相同的內存語義

鎖的內存語義的實現原理

內置鎖

內置鎖,即使用 synchronized 形成的鎖。

內置鎖依賴于 JVM。編譯器會在同步塊的入口位置和退出位置分別插入 monitorenter 和 monitorexit字節碼指令。而對于 synchronized 方法,編譯器會在 Class 文件的方法表中將該方法的 access_flags 字段中的 synchronized 標志位置 1,表示該方法是同步方法并使用調用該方法的對象。

顯式鎖

以 ReentrantLock(分為公平鎖和非公平鎖) 為例:

公平鎖 加鎖:首先會調用 getState() 方法讀 volatile 變量 state; 解鎖:setState(int newState) 方法寫 volatile 變量 state。 實質上還是在使用 volatile 共享變量。

非公平鎖 加鎖:首先會使用 CAS 更新 volatile 變量 state,更新不成功再去采用公平鎖的方式(比較粗魯) 解鎖:setState(int newState) 方法寫 volatile 變量 state。 CAS 先讀后寫,CAS 讀(volatile 讀)不會與后面的任何操作重排序,CAS 寫(volatile 寫)不會與前面的任何操作重排序,所以 CAS 操作不會與 CAS 前面和后面的任意操作重排序。

利用了 CAS 附帶 volatile 變量實現。

疑難問題

Q:為什么 volatile 寫只加入了Store-Store屏障呢,這樣普通讀不就可以重拍到volatile寫的下方了?


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 年辖:市辖区| 保德县| 阳新县| 读书| 清镇市| 井冈山市| 平顺县| 黎城县| 高邑县| 元氏县| 肃南| 铁力市| 安国市| 沾化县| 怀安县| 曲周县| 福州市| 九江县| 碌曲县| 张北县| 府谷县| 托里县| 白山市| 龙海市| 西吉县| 清丰县| 和硕县| 南通市| 昆山市| 岢岚县| 缙云县| 中西区| 辛集市| 古浪县| 永城市| 崇明县| 锦屏县| 赤水市| 嘉禾县| 托克逊县| 武强县|