之前在InfoQ看到一篇關於java重排序的一篇文章,覺得裏面有些知識寫得太絕對了,於是想通過實際程序來說明一下:
關於java重排序,這裏就不做介紹了,我們知道JVM底層封裝了與OS的交互,它內部有自己的一套類似於OS的內存模型,程序重排序的設計思路基本上是來源於OS跟硬件層面的設計。下面直接入正題吧!
我們知道JVM給每個線程分配了自己的內存空間,也就是說在變量存儲方面,分爲主內存和線程工作內存,也就是說,所有線程共享主內存,每個線程都有自己的工作內存。程序執行的時候是去工作內存裏面取值還是去主內存裏面取值呢?下面以代碼爲例:
public class DemoWork { private boolean stop=false; private boolean start=true; public void workThread() throws InterruptedException{ Thread workThread=new Thread(new Runnable() { private int i=0; @Override public void run() { // TODO Auto-generated method stub while(!stop){ i++; /*try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }*/ } start=false; } }); workThread.start(); Thread.sleep(1000); stop=true; Thread printThread=new Thread(new Runnable() { private int i=0; @Override public void run() { // TODO Auto-generated method stub while(stop&&start){ System.out.println("stop is:"+stop); try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); printThread.start(); } /** * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub DemoWork dw=new DemoWork(); dw.workThread(); } }
上面的代碼是不會停下來的,但是如果把sleep那段代碼的註釋去掉程序就能停下來了,這是什麼原因呢?我的理解是:因爲線程printThread是能正常執行的,所以有兩種可能:
線程workThread裏面工作線程stop變量值沒有收到主存的同步,而它一直取的是自己工作線程裏面的stop值
主線程更新stop沒有更新主內存,以至於主內存裏面保存的stop值一直是false
以上第二點我覺得是可以排除的,因爲線程printThread裏面的值stop值是true,所以造成以上情況第一點的可能性大一點,那爲什麼把workThread裏面的睡眠去掉之後程序又能正常退出呢?那就應該是在執行這些語句的時候主內存更新了工作內存的緣故了(執行打印語句也會推出,至於這裏面的原因是什麼,暫時還沒看到相關的資料,可能跟JVM的重排序規則有關係,但是規則到底是怎樣的呢?),接下來我們來說說volatile。
volatile:
(適用於Java所有版本)讀和寫一個volatile變量有全局的排序。也就是說每個線程訪問一個volatile作用域時會在繼續執行之前讀取它的當前值,而不是(可能)使用一個緩存的值。(但是並不保證經常讀寫volatile作用域時讀和寫的相對順序,也就是說通常這並不是有用的線程構建)。
(適用於Java5及其之後的版本)volatile的讀和寫建立了一個happens-before關係,類似於申請和釋放一個互斥鎖[7]。
也就是說在上面workThread線程sleep代碼段註釋的情況下,我們可以使用volatile來修飾stop變量,這樣的話就能強制workThread線程去主內存裏面取stop的值了,但是這樣做的話在高並發現會造成性能問題。之前看了很多的開源代碼,裏面解決以上主內存與工作內存不同步的方式基本上是採用volatile修飾變量解決的。我在想,既然volatile在併發情況下會造成性能問題,在workThread循環快裏面執行什麼類型的代碼快能方便JVM更好的同步主內存跟工作內存的值,那樣的話,在高併發下,就能更快的提高程序性能了。