Java中和執行緒相關的關鍵字就兩:volatile和synchronized。
volatile以前用得較少,以後會用得更少(後面解釋)。它是一種非常輕量級的同步機制,它的三大特性是:
1、保證可見性,即強制將CPU快取記憶體的資料立即寫入主記憶體,會導致其他CPU核中對應的快取記憶體內容無效,就像這樣:
如果由於同步需要,某行程式碼使用了volatile關鍵字,那麼當CPU核心1收到指令時,會立即將它位於快取記憶體中的資料(這裡是字串「1」)寫到主記憶體中去,那麼其餘的資料(「哈哈」、「test」、9.9)會全部失效。
2、有序性(禁止指令重排)
所謂指令重排,就是對於int a = 0、 int b = 1這類賦值語句,編譯成class位元組碼時,a = 0,b = 1的順序可能會因為編譯器的優化而導致和書寫時的順序不一致。但如果有c = a + b,那麼這行程式碼必須在前兩句的後面執行。這個不用深究,知道就好了。
3、不保證原子性
所謂不保證原子性,就是如果遇到多個執行緒同時執行i = i + 1,那麼i可能就不能保證僅僅被加1了。
根據Java的記憶體模型整理出如下這些規則,看看就好,能理解就記住,理解不了也沒關係,不用記:
下面還是以程式碼來說明:
public class VolatileTest extends Thread { static boolean flag = true; @Override public void run() { while (flag) { } System.out.println("子執行緒結束"); } public static void main(String[] args) throws InterruptedException { new Thread(new VolatileTest()).start(); TimeUnit.SECONDS.sleep(1); flag = false; System.out.println("主執行緒結束"); } }
加上volatile之後,子執行緒就不會一直卡住了:
public class VolatileTest2 extends Thread { volatile static boolean flag = true; @Override public void run() { while (flag) { } System.out.println("子執行緒結束"); } public static void main(String[] args) throws InterruptedException { new Thread(new VolatileTest2()).start(); TimeUnit.SECONDS.sleep(1); flag = false; System.out.println("主執行緒結束"); } }
其實在第一個類VolatileTest中,即使不用volatile關鍵字,也能讓程式正常結束,只需要新增一行程式碼就行了:
public class VolatileTest3 extends Thread { static boolean flag = true; @Override public void run() { while (flag) { // 只要在這裡隨便加一行程式碼,雖然原理不同,但效果和加了volatile一樣 System.out.println(" "); } System.out.println("子執行緒結束"); } public static void main(String[] args) throws InterruptedException { new Thread(new VolatileTest3()).start(); TimeUnit.SECONDS.sleep(1); flag = false; System.out.println("主執行緒結束"); } }
總結一下,適應volatile的條件:
1、對變數的寫操作不依賴於當前值,就是不會出現 x++ 或 x = x + y 這樣的自增性質的語句;
2、變數沒有包含在具有其他變數的程式碼中,就是不會出現 if(x > y) { // dosomething; } 這樣的語句,因為這樣的話,dosomething就不是執行緒安全的。
所以適合使用volatile的場景是:
1、狀態標記量
2、雙重檢查
狀態標記量:只設定簡單的狀態值,希望立即看到,且無其他變數參與。之前Volatile2中的flag就是這樣的狀態標記量。
雙重檢查(這個有點生僻冷門,不深究)。
因為目前synchronized的效能已經有了大幅提升,而且機器硬體的效能也較之前翻了幾十倍,volatile存在的意義已經不大了。所以如果對volatile的理解不夠深入,就乾脆不用理解了。
再來看另一個重量級選手:synchronized
synchronized關鍵字用於解決多個執行緒之間的資源共用問題,通常有三大作用域:
1、靜態方法:public synchronized static void methodName(),進入方法前要獲得當前類物件的鎖;
2、實體方法:public synchronized void methodName(),進入方法前要獲得當前物件範例的鎖;
3、程式碼塊:最常用,指定一個特別的加鎖物件,進入同步程式碼塊前要獲得這個物件鎖。
這幾個就不一個個地說了,因為網上這類例子太多了。
就簡單說一下volatile和synchronized的比較:
1、volatile是synchronized的輕量級實現,效能要比synchronized要好;
2、volatile只能修飾變數,synchronized只能修飾方法和程式碼塊;
3、volatile不會造成阻塞,synchronized會。
最後再說一個我之前遇到的小問題:
如果使用的IDE是Eclipse,那麼當前活動的執行緒數量可能是1;
但如果使用的IDE是IDEA,那麼當前活動的執行緒數量可能是2;
有圖為證:
這是因為在IDEA中多了一個Monitor Ctrl-Break。