synchronized和volatile理解
一,volatile關鍵字的可見性
要想理解volatile關鍵字,得先了解下JAVA的記憶體模型,Java記憶體模型的抽象示意圖如下:
從圖中可以看出:
①每個執行緒都有一個自己的本地記憶體空間–執行緒棧空間???執行緒執行時,先把變數從主記憶體讀取到執行緒自己的本地記憶體空間,然後再對該變數進行操作
②對該變數操作完後,在某個時間再把變數重新整理回主記憶體
關於JAVA記憶體模型,更詳細的可參考: 深入理解Java記憶體模型(一)——基礎
因此,就存在記憶體可見性問題,看一個示例程式:(摘自書上)
public class RunThread extends Thread {
private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public void run() {
System.out.println("進入到run方法中了");
while (isRunning == true) {
}
System.out.println("執行緒執行完成了");
}
}
public class Run {
public static void main(String[] args) {
try {
RunThread thread = new RunThread();
thread.start();
Thread.sleep(1000);
thread.setRunning(false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Run.java 第28行,main執行緒 將啟動的執行緒RunThread中的共享變數設定為false,從而想讓RunThread.java 第14行中的while迴圈結束。
如果,我們使用JVM -server引數執行該程式時,RunThread執行緒並不會終止!從而出現了死迴圈!!
原因分析:
現在有兩個執行緒,一個是main執行緒,另一個是RunThread。它們都試圖修改 第三行的 isRunning變數。按照JVM記憶體模型,main執行緒將isRunning讀取到本地執行緒記憶體空間,修改後,再重新整理回主記憶體。
而在JVM 設定成 -server模式執行程式時,執行緒會一直在私有堆疊中讀取isRunning變數。因此,RunThread執行緒無法讀到main執行緒改變的isRunning變數
從而出現了死迴圈,導致RunThread無法終止。這種情形,在《Effective JAVA》中,將之稱為“活性失敗”
解決方法,在第三行程式碼處用 volatile 關鍵字修飾即可。這裡,它強制執行緒從主記憶體中取 volatile修飾的變數。
volatile private boolean isRunning = true;
擴充套件一下,當多個執行緒之間需要根據某個條件確定 哪個執行緒可以執行時,要確保這個條件在 執行緒 之間是可見的。因此,可以用volatile修飾。
綜上,volatile關鍵字的作用是:使變數在多個執行緒間可見(可見性)
二,volatile關鍵字的非原子性
所謂原子性,就是某系列的操作步驟要麼全部執行,要麼都不執行。
比如,變數的自增操作 i++,分三個步驟:
①從記憶體中讀取出變數 i 的值
②將 i 的值加1
③將 加1 後的值寫回記憶體
這說明 i++ 並不是一個原子操作。因為,它分成了三步,有可能當某個執行緒執行到了第②時被中斷了,那麼就意味著只執行了其中的兩個步驟,沒有全部執行。
關於volatile的非原子性,看個示例:
public class MyThread extends Thread {
public volatile static int count;
private static void addCount() {
for (int i = 0; i < 100; i++) {
count++;
}
System.out.println("count=" + count);
}
@Override
public void run() {
addCount();
}
}
public class Run {
public static void main(String[] args) {
MyThread[] mythreadArray = new MyThread[100];
for (int i = 0; i < 100; i++) {
mythreadArray[i] = new MyThread();
}
for (int i = 0; i < 100; i++) {
mythreadArray[i].start();
}
}
}
MyThread類第2行,count變數使用volatile修飾
Run.java 第20行 for迴圈中建立了100個執行緒,第25行將這100個執行緒啟動去執行 addCount(),每個執行緒執行100次加1
期望的正確的結果應該是 100*100=10000,但是,實際上count並沒有達到10000
原因是:volatile修飾的變數並不保證對它的操作(自增)具有原子性。(對於自增操作,可以使用JAVA的原子類AutoicInteger類保證原子自增)
比如,假設 i 自增到 5,執行緒A從主記憶體中讀取i,值為5,將它儲存到自己的執行緒空間中,執行加1操作,值為6。此時,CPU切換到執行緒B執行,從主從記憶體中讀取變數i的值。由於執行緒A還沒有來得及將加1後的結果寫回到主記憶體,執行緒B就已經從主記憶體中讀取了i,因此,執行緒B讀到的變數 i 值還是5
相當於執行緒B讀取的是已經過時的資料了,從而導致執行緒不安全性。這種情形在《Effective JAVA》中稱之為“安全性失敗”
綜上,僅靠volatile不能保證執行緒的安全性。(原子性)
此外,volatile關鍵字修飾的變數不會被指令重排序優化。這裡以《深入理解JAVA虛擬機器》中一個例子來說明下自己的理解:
執行緒A執行的操作如下:
Map configOptions ;
char[] configText;
volatile boolean initialized = false;
//執行緒A首先從檔案中讀取配置資訊,呼叫process…處理配置資訊,處理完成了將initialized 設定為true
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfig(configText, configOptions);//負責將配置資訊configOptions 成功初始化
initialized = true;
執行緒B等待執行緒A把配置資訊初始化成功後,使用配置資訊去幹活…..執行緒B執行的操作如下:
while(!initialized)
{
sleep();
}
//使用配置資訊幹活
doSomethingWithConfig();
如果initialized變數不用 volatile 修飾,線上程A執行的程式碼中就有可能指令重排序。
即:執行緒A執行的程式碼中的最後一行:initialized = true 重排序到了 processConfig方法呼叫的前面執行了,這就意味著:配置資訊還未成功初始化,但是initialized變數已經被設定成true了。那麼就導致 執行緒B的while迴圈“提前”跳出,拿著一個還未成功初始化的配置資訊去幹活(doSomethingWithConfig方法)。。。。
因此,initialized 變數就必須得用 volatile修飾。這樣,就不會發生指令重排序,也即:只有當配置資訊被執行緒A成功初始化之後,initialized 變數才會初始化為true。綜上,volatile 修飾的變數會禁止指令重排序(有序性)
三,volatile 與 synchronized 的比較
volatile主要用在多個執行緒感知例項變數被更改了場合,從而使得各個執行緒獲得最新的值。它強制執行緒每次從主記憶體中講到變數,而不是從執行緒的私有記憶體中讀取變數,從而保證了資料的可見性。
關於synchronized,可參考:JAVA多執行緒之Synchronized關鍵字–物件鎖的特點
比較:
①volatile輕量級,只能修飾變數。synchronized重量級,還可修飾方法
②volatile只能保證資料的可見性,不能用來同步,因為多個執行緒併發訪問volatile修飾的變數不會阻塞。
synchronized不僅保證可見性,而且還保證原子性,因為,只有獲得了鎖的執行緒才能進入臨界區,從而保證臨界區中的所有語句都全部執行。多個執行緒爭搶synchronized鎖物件時,會出現阻塞。
四,執行緒安全性
執行緒安全性包括兩個方面,①可見性。②原子性。
從上面自增的例子中可以看出:僅僅使用volatile並不能保證執行緒安全性。而synchronized則可實現執行緒的安全性。
參考:JAVA多執行緒之執行緒間的通訊方式
JAVA多執行緒之當一個執行緒在執行死迴圈時會影響另外一個執行緒嗎?
JAVA多執行緒之wait/notify
文章內容來自網上資源,只為記錄相關知識,如有侵權,請聯絡作者。
相關文章
- 理解並正確使用synchronized和volatilesynchronized
- synchronized和volatile的區別synchronized
- 全面解讀volatile和synchronize,輕鬆掌握Volatile與Synchronizedsynchronized
- Volatile關鍵字&&DCL單例模式,volatile 和 synchronized 的區別單例模式synchronized
- 多執行緒基礎之synchronized和volatile執行緒synchronized
- volatile與synchronized的區別synchronized
- Java併發2:JMM,volatile,synchronized,finalJavasynchronized
- volatile的理解
- 打工人,從 JMM 透析 volatile 與 synchronized 原理synchronized
- 深入理解volatile
- 徹底理解volatile
- volatile和synchronized到底啥區別?多圖文講解告訴你synchronized
- 徹底理解synchronizedsynchronized
- 從JMM透析volatile與synchronized原理,圖文並茂synchronized
- 怎樣正確理解volatile?
- 死磕Java——volatile的理解Java
- 快速理解 volatile 關鍵字
- 詳解鎖原理,synchronized、volatile+cas底層實現synchronized
- Synchronized ,Volatile,Lock 三者不可告人的祕密synchronized
- 10-Java中共享記憶體可見性以及synchronized和volatile關鍵字Java記憶體synchronized
- Java關鍵字volatile的理解Java
- 面試:為了進阿里,重新翻閱了Volatile與Synchronized面試阿里synchronized
- 併發程式設計之ThreadLocal、Volatile、synchronized、Atomic關鍵字程式設計threadsynchronized
- 既然synchronized是"萬能"的,為什麼還需要volatile呢?synchronized
- 從CPU Cache出發徹底弄懂volatile/synchronized/cas機制synchronized
- 多執行緒的指令重排問題:as-if-serial語義,happens-before語義;volatile關鍵字,volatile和synchronized的區別執行緒APPsynchronized
- 深入理解synchronized關鍵字synchronized
- java多執行緒之volatile理解Java執行緒
- 兩張圖理解volatile關鍵字
- 帶你深入理解和解剖 synchronizedsynchronized
- java裡的鎖總結(synchronized隱式鎖、Lock顯式鎖、volatile、CAS)Javasynchronized
- 基礎篇:詳解鎖原理,volatile+cas、synchronized的底層實現synchronized
- 深入彙編指令理解Java關鍵字volatileJava
- 深入理解Java中的volatile關鍵字Java
- transient和synchronized的使用synchronized
- 對volatile的理解--從JMM以及單例模式剖析單例模式
- Lock 和 synchronized的區別synchronized
- C++ 中的 volatile 和 atomicC++
- 淺談synchronized、Lock、ThreadLocal和semaphoresynchronizedthread