volatile關鍵字解析(二)
阿新 • • 發佈:2018-01-22
禁止 new incr lock 解析 static style ron running
volatile詳解
接下來,我們詳細講述一下volatile關鍵字
volatile關鍵字具有兩重語義
- 保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這個新值對其他線程來說是立即可見的。
- 禁止指令重排序
依然使用上文RunThread案例說明,上文中已經提到了,由於主線程修改了isRunning的值後,RunThread線程並沒有看到其值的變化,因此,進入了死循環,但是當isRunning加上volatile關鍵字後,效果就不一樣了。
- 使用volatile關鍵字會強制將修改的值立即寫入主存;
- 使用volatile關鍵字的話,當主線程修改時,會導致RunThread的工作內存中isRunning變量的緩存值變得無效。
- 由於RunThread的工作內存中緩存變量isRunning緩存無效,所以會再次從主存中讀取isRunning變量值。
這樣就保證了RunThread可以順利結束。
關於原子性,我們先看個例子:
public class TestCount { private volatile int n; public void increase() { n ++; } public static void main(String[] args) { TestCount count = new TestCount();for(int i=0; i<10; i++) { new Thread(){ public void run() { for(int j=0;j<1000;j++) count.increase(); }; }.start(); } if(Thread.activeCount() > 1) { Thread.yield(); } System.out.println(count.n); } }
關於這個結果,應該很多人會認為是10000吧。但是實際上,運行結果小於10000,這是由於n++操作並非原子性操作,其涉及三個操作
- 讀出n的舊知
- 對n+1
- 寫入新值
雖然volatile可以保證每個線程讀取到的是最新的內存值,但是,如果10個線程第一步都是正確的,第二步和第三步單線程看的話也都是沒有問題的,但是如果並發的話,就會導致同一個值被多次寫入了內存。
可見volatile並不能保證原子性操作。
對於這一類問題,我們可以通過synchronized、lock或者原子操作類經行操作。
synchronized版本:
public class TestCount { public int inc = 0; public synchronized void increase() { inc++; } public static void main(String[] args) { final TestCount test = new TestCount(); for(int i=0;i<10;i++){ new Thread(){ public void run() { for(int j=0;j<1000;j++) test.increase(); }; }.start(); } while(Thread.activeCount()>1) //保證前面的線程都執行完 Thread.yield(); System.out.println(test.inc); } }
lock版本:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TestCount { public int inc = 0; Lock lock = new ReentrantLock(); public void increase() { lock.lock(); try { inc++; } finally{ lock.unlock(); } } public static void main(String[] args) { final TestCount test = new TestCount(); for(int i=0;i<10;i++){ new Thread(){ public void run() { for(int j=0;j<1000;j++) test.increase(); }; }.start(); } while(Thread.activeCount()>1) //保證前面的線程都執行完 Thread.yield(); System.out.println(test.inc); } }
原子操作類版本:
import java.util.concurrent.atomic.AtomicInteger; public class TestCount { public AtomicInteger inc = new AtomicInteger(); public void increase() { inc.getAndIncrement(); } public static void main(String[] args) { final TestCount test = new TestCount(); for(int i=0;i<10;i++){ new Thread(){ public void run() { for(int j=0;j<1000;j++) test.increase(); }; }.start(); } while(Thread.activeCount()>1) //保證前面的線程都執行完 Thread.yield(); System.out.println(test.inc); } }
對於有序性,我們可以看下下面的這個例子:
//線程1: context = loadContext(); //語句1 inited = true; //語句2 //線程2: while(!inited ){ sleep() } doSomethingwithconfig(context);
如果沒有對inited加volatile關鍵字,則可能會對語句1和語句2進行調整,因此兩者並無依賴關系,並且單線程下,也並不會影響結果。但是兩個線程下,可能由於context並沒有初始化完成,導致意想不到的結果。
如果對inited加上volatile,這個問題就不會發生了。語句1和語句2的執行順序則不會被調整。
volatile關鍵字解析(二)