轉自:http://www.importnew.com/19434.html
博文前提
最近在oschina問答板塊看到了一個關於java變量在工作內存和主存中的可見性問題:synchorized,sleep 也能達到volatile 線程可見性的目的?,大致的問題描述如下:
package com.test; import java.util.concurrent.TimeUnit; public class Test01 { private static boolean is = true; public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { int i = 0; while (Test01.is) { i++; // byte[] bts = new byte[1024*1024*2]; // byte[] bts1 = new byte[1024*1024*1]; // byte[] bts2 = new byte[1024*1024*3]; // 1. synchronized (this) { } 會強制刷新主內存的變量值到線程棧? // 2. System.out.println("1"); println 是synchronized 的,會強制刷新主內存的變量值到線程棧? // 3. sleep 會從新load主內存的值? // try { // TimeUnit.MICROSECONDS.sleep(1); // }catch (InterruptedException e) { // e.printStackTrace(); // } } } }).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new Runnable() { @Override public void run() { // 設置is為false,使上面的線程結束while循環 Test01.is = false; } }).start(); } }
問:
為什么整個程序不會終止?
為什么取消注釋中的任何一個代碼塊(1,2,3),程序才會終止?
synchronized 會強制刷新住內存的變量值到線程棧?
sleep 會干什么呢?
涉及知識解釋
- volatile:此關鍵字保證了變量在線程的可見性,所有線程訪問由volatile修飾的變量,都必須從主存中讀取后操作,並在工作內存修改后立即寫回主存,保證了其他線程的可見性,同樣效果的關鍵字還有final。
- synchronized:所有同步操作都必須保證 1、原子性 2、可見性,所以在同步塊中發生的變化會立馬寫回主存
- sleep:此方法只會讓出CPU執行時間,並不會釋放鎖。
問題分析
Q1:為什么注釋代碼后程序不會終止?
A1:因為 boolean is=true 的變量值被前面線程(簡稱線程A)加載到自己的工作內存,在后面的線程(簡稱線程B)改變 boolean is=false 之后不一定會立馬寫入主存(不過這道題中應該會馬上寫入主存,因為線程執行完 is=false之后線程就要退出了),即便立馬寫入了主存后線程A也不一定馬上load到工作內存中,所以程序一直不會終止?這個是我們大多數人想到的,但其實JVM針對現在的硬件水平已經做了很大程度的優化,基本上很大程度的保障了工作內存和主內存的及時同步,相當於默認使用了volatile。但只是最大程度!在CPU資源一直被占用的時候,工作內存與主內存中間的同步,也就是變量的可見性就會不那么及時!后面會驗證結論。
Q2:為什么取消注釋中的任何一個代碼塊(1,2,3),程序才會終止?
A2:行號為1、2的代碼有一個共同特點,就是都涉及到了synchronized 同步鎖,那么是否像提問作者猜想的那樣synchronized會強制刷新主內存的變量值到線程棧?
,以及sleep方法也會刷新主存的變量值到線程棧呢?
,事實上我們前面說了synchronized只會保證在同步塊
中的變量的可見性,而is
變量並不在該同步塊中,所以顯然不是這個導致的。
接下來我們在代碼i++;
后面加上以下代碼:
byte[] bts = new byte[1024*1024*2]; byte[] bts1 = new byte[1024*1024*1]; byte[] bts2 = new byte[1024*1024*3];
再Run,程序立刻終止!為什么?
在上面的 A1 中我們已經說了即便有JVM的優化,但當CPU一直被占用的時候,數據的可見性得不到很好的保證,就像上面的程序一直循環做i++;運算
占用CPU,而為什么加上上面的代碼后程序就會停止呢?因為對於幾個new byte[1024*1024*1]操作來說,CPU已經不是主要占時間的操作,真正的耗時應該在內存的分配上(因為CPU的處理速度明顯快過內存,不然也不會有CPU的寄存器了)
,所以CPU空閑后會遵循JVM優化基准,盡可能快的保證數據的可見性,從而從主存同步is變量到工作內存,最終導致程序結束,這也是為什么sleep()
方法雖然沒有涉及同步操作,但是依然可以使程序終止,因為sleep()
方法會釋放CPU,但不釋放鎖!