【70期】面試官:對并發(fā)熟悉嗎?談?wù)剬olatile的使用及其原理
閱讀本文大概需要 8 分鐘。
來自:www.cnblogs.com/paddix/p/5428507.html
一、volatile的作用
二、volatile的使用
1、防止重排序
package?com.paddx.test.concurrent;
public?class?Singleton?{
????public?static?volatile?Singleton?singleton;
????/**
?????*?構(gòu)造函數(shù)私有,禁止外部實例化
?????*/
????private?Singleton()?{};
????public?static?Singleton?getInstance()?{
????????if?(singleton?==?null)?{
????????????synchronized?(singleton)?{
????????????????if?(singleton?==?null)?{
????????????????????singleton?=?new?Singleton();
????????????????}
????????????}
????????}
????????return?singleton;
????}
}
分配內(nèi)存空間。
初始化對象。
將內(nèi)存空間的地址賦值給對應(yīng)的引用。
分配內(nèi)存空間。
將內(nèi)存空間的地址賦值給對應(yīng)的引用。
初始化對象
2、實現(xiàn)可見性
package?com.paddx.test.concurrent;
public?class?VolatileTest?{
????int?a?=?1;
????int?b?=?2;
????public?void?change(){
????????a?=?3;
????????b?=?a;
????}
????public?void?print(){
????????System.out.println("b="+b+";a="+a);
????}
????public?static?void?main(String[]?args)?{
????????while?(true){
????????????final?VolatileTest?test?=?new?VolatileTest();
????????????new?Thread(new?Runnable()?{
????????????????@Override
????????????????public?void?run()?{
????????????????????try?{
????????????????????????Thread.sleep(10);
????????????????????}?catch?(InterruptedException?e)?{
????????????????????????e.printStackTrace();
????????????????????}
????????????????????test.change();
????????????????}
????????????}).start();
????????????new?Thread(new?Runnable()?{
????????????????@Override
????????????????public?void?run()?{
????????????????????try?{
????????????????????????Thread.sleep(10);
????????????????????}?catch?(InterruptedException?e)?{
????????????????????????e.printStackTrace();
????????????????????}
????????????????????test.print();
????????????????}
????????????}).start();
????????}
????}
}
......
b=2;a=1
b=2;a=1
b=3;a=3
b=3;a=3
b=3;a=1
b=3;a=3
b=2;a=1
b=3;a=3
b=3;a=3
......
3、保證原子性
17.7 Non-Atomic Treatment of double and long
For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.Writes and reads of volatile long and double values are always atomic.
Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.
Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32-bit values. For efficiency's sake, this behavior is implementation-specific; an implementation of the Java Virtual Machine is free to perform writes to long and double values atomically or in two parts.
Implementations of the Java Virtual Machine are encouraged to avoid splitting 64-bit values where possible. Programmers are encouraged to declare shared 64-bit values as volatile or synchronize their programs correctly to avoid possible complications.
package?com.paddx.test.concurrent;
public?class?VolatileTest01?{
????volatile?int?i;
????public?void?addI(){
????????i++;
????}
????public?static?void?main(String[]?args)?throws?InterruptedException?{
????????final??VolatileTest01?test01?=?new?VolatileTest01();
????????for?(int?n?=?0;?n?1000;?n++)?{
????????????new?Thread(new?Runnable()?{
????????????????@Override
????????????????public?void?run()?{
????????????????????try?{
????????????????????????Thread.sleep(10);
????????????????????}?catch?(InterruptedException?e)?{
????????????????????????e.printStackTrace();
????????????????????}
????????????????????test01.addI();
????????????????}
????????????}).start();
????????}
????????Thread.sleep(10000);//等待10秒,保證上面程序執(zhí)行完成
????????System.out.println(test01.i);
????}
}

讀取i的值。
對i加1。
將i的值寫回內(nèi)存。
注:上面幾段代碼中多處執(zhí)行了Thread.sleep()方法,目的是為了增加并發(fā)問題的產(chǎn)生幾率,無其他作用。
三、volatile的原理
1、可見性實現(xiàn):
修改volatile變量時會強(qiáng)制將修改后的值刷新的主內(nèi)存中。
修改volatile變量后會導(dǎo)致其他線程工作內(nèi)存中對應(yīng)的變量值失效。因此,再讀取該變量值的時候就需要重新從讀取主內(nèi)存中的值。
2、有序性實現(xiàn):
Two actions can be ordered by a happens-before relationship.If one action happens before another, then the first is visible to and ordered before the second.
Each action in a thread happens before every subsequent action in that thread.
An unlock on a monitor happens before every subsequent lock on that monitor.
A write to a volatile field happens before every subsequent read of that volatile.
A call to start() on a thread happens before any actions in the started thread.
All actions in a thread happen before any other thread successfully returns from a join() on that thread.
If an action a happens before an action b, and b happens before an action c, then a happens before c.
同一個線程中的,前面的操作 happen-before 后續(xù)的操作。(即單線程內(nèi)按代碼順序執(zhí)行。但是,在不影響在單線程環(huán)境執(zhí)行結(jié)果的前提下,編譯器和處理器可以進(jìn)行重排序,這是合法的。換句話說,這一是規(guī)則無法保證編譯重排和指令重排)。
監(jiān)視器上的解鎖操作 happen-before 其后續(xù)的加鎖操作。(Synchronized 規(guī)則)
對volatile變量的寫操作 happen-before 后續(xù)的讀操作。(volatile 規(guī)則)
線程的start() 方法 happen-before 該線程所有的后續(xù)操作。(線程啟動規(guī)則)
線程所有的操作 happen-before 其他線程在該線程上調(diào)用 join 返回成功后的操作。
如果 a happen-before b,b happen-before c,則a happen-before c(傳遞性)。

3、內(nèi)存屏障

LoadLoad 屏障
執(zhí)行順序:Load1—>Loadload—>Load2
確保Load2及后續(xù)Load指令加載數(shù)據(jù)之前能訪問到Load1加載的數(shù)據(jù)。StoreStore 屏障
執(zhí)行順序:Store1—>StoreStore—>Store2
確保Store2以及后續(xù)Store指令執(zhí)行前,Store1操作的數(shù)據(jù)對其它處理器可見。LoadStore 屏障
執(zhí)行順序:Load1—>LoadStore—>Store2
確保Store2和后續(xù)Store指令執(zhí)行前,可以訪問到Load1加載的數(shù)據(jù)。StoreLoad 屏障
執(zhí)行順序: Store1—> StoreLoad—>Load2
確保Load2和后續(xù)的Load指令讀取之前,Store1的數(shù)據(jù)對其他處理器是可見的。
package?com.paddx.test.concurrent;
public?class?MemoryBarrier?{
????int?a,?b;
????volatile?int?v,?u;
????void?f()?{
????????int?i,?j;
????????i?=?a;
????????j?=?b;
????????i?=?v;
????????//LoadLoad
????????j?=?u;
????????//LoadStore
????????a?=?i;
????????b?=?j;
????????//StoreStore
????????v?=?i;
????????//StoreStore
????????u?=?j;
????????//StoreLoad
????????i?=?u;
????????//LoadLoad
????????//LoadStore
????????j?=?b;
????????a?=?i;
????}
}
四、總結(jié)
對變量的寫操作不依賴于當(dāng)前值。
該變量沒有包含在具有其他變量的不變式中。
推薦閱讀:
【69期】面試官:對并發(fā)熟悉嗎?談?wù)劸€程間的協(xié)作(wait/notify/sleep/yield/join)
【68期】面試官:對并發(fā)熟悉嗎?說說Synchronized及實現(xiàn)原理
【67期】談?wù)凜oncurrentHashMap是如何保證線程安全的?
微信掃描二維碼,關(guān)注我的公眾號
朕已閱?

