Java多執行緒-執行緒關鍵字(二)

2022-11-01 15:00:41

Java中和執行緒相關的關鍵字就兩:volatilesynchronized

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。