在內存模型基礎中已經(jīng)提到過,JVM是分為堆內存和棧內存的,堆內存在線程之間共享,而棧內存為線程內部私有,對其他線程不可見。為保證變量的可見性,可以使用volatile修飾,那為什么使用了關鍵字volatile修飾后,就能保證可見性,下面進行分析醒陆。
volatile 可以看作是一個輕量級的鎖,這么說可能是不準確的,但它確實具備了鎖的一些特性。與鎖的區(qū)別是,鎖保證原子性,鎖住的可能是是個變量或者一段代碼,而volatile修飾的變量只能保證變量在線程之間的傳遞,只能保證可見性,在一些方面并沒有具備原子性警检。
所以上面的話有兩層語義:
- 保證可見性,不保證原子性
- 禁止指令的重排序(重排序會破壞volatile的內存語義)
volatile變量的讀寫可以實現(xiàn)線程之間的通信孙援。
volatile內存語義
- 寫的內存語義: 當寫一個volatile變量時,JMM會把線程對應的本地內存中的共享變量刷新到主內存。
- 讀的內存語義: 當讀一個volatile變量時扇雕,JMM會把線程對應的本地內存置為無效拓售,線程接下來從主內存中讀取共享變量。
初始時镶奉,兩個線程的本地內存中的flag和a都是初始狀態(tài)础淤,線程A在寫flag變量后,本地內存A中更新過的兩個共享變量的值被刷新到主內存中哨苛,
在讀flag變量后鸽凶,本地內存中包含的值已經(jīng)被置為無效,此時線程B必須從主內存中讀取共享變量建峭。
jdk1.5以后玻侥,對每一個線程做了優(yōu)化,每個線程都加了一塊獨立的內存空間亿蒸。內部裝了主內存變量的副本凑兰,該線程直接取內存空間副本的值。
/**
* @Description TODO
* @Author "zhouhai"
* @Date2018/10/816:46
**/
public class RunThread extends Thread{
private "volatile" boolean isRunning = true;
private void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
public void run() {
System.out.println("進入run方法..");
while (isRunning == true) {
}
System.out.println("線程停止");
}
public static void main(String[] args) throws InterruptedException {
RunThread rt = new RunThread();
rt.start();
Thread.sleep(3000);
rt.setRunning(false);
System.out.println("isRunning的值已經(jīng)被設置了false");
Thread.sleep(1000);
System.out.println(rt.isRunning);
}
}
加了volatile后的運行結果:
進入run方法..
isRunning的值已經(jīng)被設置了false
線程停止
false
同一份變量在rt線程和main線程實現(xiàn)了可見性
從匯編層看volatile關鍵字
記得曾經(jīng)看過一篇文章边锁,講述的是volatile關鍵字修飾的變量姑食,編程匯編代碼后,會在變量的前面插入一條LOCK指令茅坛。
Java代碼: instance = new Singleton();//instance是volatile變量
匯編代碼: 0x01a3de1d: movb0x0,(%esp);
通過上面的代碼發(fā)現(xiàn)音半,volatile修飾的變量會多出一個lock指令,LOCK指令屬于系統(tǒng)層級: LOCK前綴會使處理器執(zhí)行當前指令時產生一個LOCK#信號贡蓖,顯示的鎖定總線曹鸠。
來看一下LOCK指令的作用:
鎖總線:其他cpu對內存的讀寫請求會被阻塞,直到鎖釋放斥铺,不過因為鎖總線的的開銷太大彻桃,后來采用鎖緩存來代替鎖總線
lock后的寫操作會回寫已修改的數(shù)據(jù),同時讓其它cpu相關緩存行失效仅父,從而重新從主存中加載最新的數(shù)據(jù)
不是內存屏障卻完成類似內存屏障的功能,阻止屏障兩邊的執(zhí)行重排序
volatile并不具備原子性
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Description TODO
* @Author "zhouhai"
* @Date2018/10/910:05
**/
public class VolatileNoAtomic extends Thread {
/**
* private static volatile int count;
*/
private static AtomicInteger count = new AtomicInteger(0);
private static void addCount() {
for (int i = 0; i < 1000; i++) {
//count++;
count.incrementAndGet();
}
System.out.println(count);
}
@Override
public void run() {
addCount();
}
public static void main(String[] args) {
VolatileNoAtomic[] arr = new VolatileNoAtomic[10];
for (int i = 0; i < 10 ; i++) {
arr[i] = new VolatileNoAtomic();
}
for (int i = 0; i < 10 ; i++) {
arr[i].start();
}
}
}
volatile并不能保證原子性浑吟,可以用AtomicInteget count 代替 volatile int count ,count.incrementAndGet()代替count++
3000
3000
3000
4000
5000
6000
7000
8000
9000
10000
要實現(xiàn)原子性建議使用atomic類的系列對象笙纤,支持原子性操作(注意atomic類只保證本身方法的原子性,并不保證多次操作的原子性)
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Description TODO
* @Author "zhouhai"
* @Date2018/10/910:19
**/
public class AtomicUse {
private static AtomicInteger count = new AtomicInteger(0);
//多個addAndGet在一個方法內是非原子性的组力,需要加synchronized進行修飾省容,保證4個addAndGet整體原子性
public int multiAdd() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count.addAndGet(1);
count.addAndGet(2);
count.addAndGet(3);
count.addAndGet(4);
return count.get();
}
public static void main(String[] args) {
final AtomicUse au = new AtomicUse();
List<Thread> ts = new ArrayList<>();
for (int i = 0; i <100 ; i++) {
ts.add(new Thread(new Runnable() {
@Override
public void run() {
System.out.println(au.multiAdd());
}
}));
}
for (Thread t : ts) {
t.start();
}
}
}
20
40
30
20
50
80
90
70
60
100
110
130
120
140
150
166
190
180
170
200
210
220
240
230
250
260
270
280
300
290
310
320
330
340
350
360
370
380
390
400
410
420
430
450
440
520
530
560
510
500
581
480
470
620
660
460
490
650
640
630
610
600
590
571
710
550
540
700
690
720
680
670
730
740
750
760
770
780
790
800
970
960
950
940
930
920
910
900
890
880
870
860
850
840
830
820
810
1000
990
980
總結
volatile是一種比鎖更輕量級的線程之間通信的機制,volatile僅僅保證對單個volatile變量的讀/寫具有原子性燎字,而鎖的互斥執(zhí)行的特性可以保證對整個臨界區(qū)代碼執(zhí)行具有原子性腥椒,在功能上阿宅,鎖比voatile更強大,在可伸縮性和執(zhí)行性能上笼蛛,volatile更有優(yōu)勢洒放,但是volatile并不能代替鎖。