Java并發(fā)系列番外篇——同步機(jī)制(二)
Java提供了一種稍弱的同步機(jī)制设凹,即volatile變量案站,用來確保將更新的操作通知到其他線程痛黎。
姊妹篇《Java同步機(jī)制之synchronized》
Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中,每條線程還有自己的工作內(nèi)存以躯,線程的工作內(nèi)存中保存了該線程中是用到的變量的主內(nèi)存副本拷貝槐秧,線程對變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存忧设。不同的線程之間也無法直接訪問對方工作內(nèi)存中的變量刁标,線程間變量的傳遞均需要自己的工作內(nèi)存和主存之間進(jìn)行數(shù)據(jù)同步進(jìn)行。
volatile通常被比喻成”輕量級(jí)的synchronized“址晕,也是Java并發(fā)編程中比較重要的一個(gè)關(guān)鍵字命雀。和synchronized不同,volatile是一個(gè)變量修飾符斩箫,只能用來修飾變量吏砂。無法修飾方法及代碼塊等。
volatile的主要作用是使變量在多個(gè)線程間可見乘客。
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ì)一直保持循環(huán)狐血,因?yàn)閷τ诰€程a來說,isStop的值可能永遠(yuǎn)不可見易核。代碼甚至?xí)蛴〕?code>結(jié)束了:0,因?yàn)樵趯um賦值之前匈织,主線程就已經(jīng)寫入isStop并對線程a可見,這是一種重排序
現(xiàn)象(<u>在執(zhí)行程序時(shí)為了提高性能牡直,提高并行度缀匕,編譯器和處理器常常會(huì)對指令做重排序</u>)。這個(gè)問題其實(shí)就是由于私有堆棧中的值和公有堆棧中的值不同步造成的碰逸。
這段代碼演示了一個(gè)沒要進(jìn)行恰當(dāng)同步的程序乡小,它引起的后果就是:數(shù)據(jù)過期。需要注意的是:只要數(shù)據(jù)需要被跨線程共享饵史,就要進(jìn)行恰當(dāng)?shù)耐健?p>
private boolean isStop = false;
private int num = 0;
通過volatile關(guān)鍵字满钟,強(qiáng)制從公共內(nèi)存中讀取變量的值:
使用volatile關(guān)鍵字增加了實(shí)例變量在多個(gè)線程之間的可見性胜榔,但是依然無法確保原子性。
對于用volatile修飾的變量湃番,jvm虛擬機(jī)只能保證從主內(nèi)存加載到線程工作內(nèi)存是最新的值夭织,但是無法確保load、use吠撮、asign操作的安全性尊惰。
總結(jié)
使用synchronized和volatile來保證多線程之間操作的有序性,它們的區(qū)別如下:
- synchronized關(guān)鍵字保證同一時(shí)刻只允許一條線程操作泥兰。
- volatile無法保證原子性(沒有同步性)弄屡,只能保證可見性,附加禁止指令重排
- synchronized既可保證原子性逾条,也可保證可見性
- synchronized有性能損耗
- synchronized會(huì)產(chǎn)生阻塞,synchronize實(shí)現(xiàn)的鎖本質(zhì)上是一種阻塞鎖
- synchronized具有將線程工作內(nèi)存中的私有變量與公共內(nèi)存中的變量同步的功能
注:上述代碼需要用JVM的Service模式啟動(dòng)JVM的server模式和client模式