Java并發(fā)系列番外篇——同步機制(二)
Java提供了一種稍弱的同步機制,即volatile變量,用來確保將更新的操作通知到其他線程。
Java內(nèi)存模型規(guī)定了所有的變量都存儲在主內(nèi)存中,每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了該線程中是用到的變量的主內(nèi)存副本拷貝,線程對變量的所有操作都必須在工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存。不同的線程之間也無法直接訪問對方工作內(nèi)存中的變量,線程間變量的傳遞均需要自己的工作內(nèi)存和主存之間進行數(shù)據(jù)同步進行。
volatile通常被比喻成”輕量級的synchronized“,也是Java并發(fā)編程中比較重要的一個關鍵字。和synchronized不同,volatile是一個變量修飾符,只能用來修飾變量。無法修飾方法及代碼塊等。
volatile的主要作用是使變量在多個線程間可見。
volatile
我們開看一段代碼:
public class MyThread extends Thread {
private boolean isStop = false;
private int num = 0;
@Override
public void run() {
while (isStop == false) {
System.out.println(num);
}
System.out.println("結(jié)束了:" + num);
}
public void setIsStop(boolean isStop) {
this.isStop = isStop;
}
public void setNum(int num) {
this.num = num;
}
}
MyThread myThread = new MyThread();
myThread.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread.setNum(10);
myThread.setIsStop(true);
這段代碼的可能會一直保持循環(huán),因為對于線程a來說,isStop的值可能永遠不可見。代碼甚至會打印出結(jié)束了:0
,因為在對num賦值之前,主線程就已經(jīng)寫入isStop并對線程a可見,這是一種重排序
現(xiàn)象(<u>在執(zhí)行程序時為了提高性能,提高并行度,編譯器和處理器常常會對指令做重排序</u>)。這個問題其實就是由于私有堆棧中的值和公有堆棧中的值不同步造成的。
這段代碼演示了一個沒要進行恰當同步的程序,它引起的后果就是:數(shù)據(jù)過期。需要注意的是:只要數(shù)據(jù)需要被跨線程共享,就要進行恰當?shù)耐健?p>
private boolean isStop = false;
private int num = 0;
通過volatile關鍵字,強制從公共內(nèi)存中讀取變量的值:
使用volatile關鍵字增加了實例變量在多個線程之間的可見性,但是依然無法確保原子性。
對于用volatile修飾的變量,jvm虛擬機只能保證從主內(nèi)存加載到線程工作內(nèi)存是最新的值,但是無法確保load、use、asign操作的安全性。
總結(jié)
使用synchronized和volatile來保證多線程之間操作的有序性,它們的區(qū)別如下:
- synchronized關鍵字保證同一時刻只允許一條線程操作。
- volatile無法保證原子性(沒有同步性),只能保證可見性,附加禁止指令重排
- synchronized既可保證原子性,也可保證可見性
- synchronized有性能損耗
- synchronized會產(chǎn)生阻塞,synchronize實現(xiàn)的鎖本質(zhì)上是一種阻塞鎖
- synchronized具有將線程工作內(nèi)存中的私有變量與公共內(nèi)存中的變量同步的功能
注:上述代碼需要用JVM的Service模式啟動JVM的server模式和client模式