歡迎光臨
每天分享高質量文章

多執行緒:為什麼在while迴圈中加入System.out.println,執行緒可以停止

精品專欄

 

在論壇看到這樣一個代碼:

  1. public class StopThread {

  2.    private static boolean stopRequested;

  3.    public static void main(String[] args) throws InterruptedException {

  4.        Thread backgroundThread = new Thread(new Runnable() {

  5.            @Override

  6.            public void run() {

  7.                int i = 0;

  8.                while (!stopRequested) {

  9.                    i++;

  10.                }

  11.            }

  12.        });

  13.        backgroundThread.start();

  14.        TimeUnit.SECONDS.sleep(1);

  15.        stopRequested = true;

  16.    }

  17. }

這個我們都知道,由於 stopReqested 的更新值在主記憶體中,而執行緒棧中的值不是最新的,所以會一直迴圈,執行緒並不能停止。加上 Volatile 關鍵字後,保證變數的最新值會被更新到主存,執行緒在讀這個變數時候,也會去取最新的,保證資料的可見性。 但是本文的意思不在此,不對 stopReqested 加同步關鍵字是否就不能停止了呢?不是的。

如下就能停止執行緒的運行:

  1. public class StopThread {

  2.    private static boolean stopRequested;

  3.    public static void main(String[] args) throws InterruptedException {

  4.        Thread backgroundThread = new Thread(new Runnable() {

  5.            @Override

  6.            public void run() {

  7.                int i = 0;

  8.                while (!stopRequested) {

  9.                    i++;

  10.                    System.out.println(""+i);

  11.                }

  12.            }

  13.        });

  14.        backgroundThread.start();

  15.        TimeUnit.SECONDS.sleep(1);

  16.        stopRequested = true;

  17.    }

  18. }

如上面所示,加了 System.out.println之後,執行緒能停止了。有的人會說,println 的原始碼裡面有 synchronized 關鍵字,所以會同步變數 stopRequested 的值。這種是很不正確的理解,同步關鍵字同步的是同步塊裡面的變數,stopRequested 在這個同步代碼塊之外。 真正的原因是這樣的:JVM 會儘力保證記憶體的可見性,即便這個變數沒有加同步關鍵字。換句話說,只要 CPU 有時間,JVM 會儘力去保證變數值的更新。這種與 volatile 關鍵字的不同在於,volatile 關鍵字會強制的保證執行緒的可見性。而不加這個關鍵字,JVM 也會儘力去保證可見性,但是如果 CPU 一直有其他的事情在處理,它也沒辦法。最開始的代碼,一直處於試了迴圈中,CPU 處於一直被飽受占用的時候,這個時候 CPU 沒有時間,JVM 也不能強制要求 CPU 分點時間去取最新的變數值。而加了 System.out.println 之後,由於內部代碼的同步關鍵字的存在,導致CPU的輸出其實是比較耗時的。這個時候CPU就有可能有時間去保證記憶體的可見性,於是while迴圈可以被終止。

其實,也可以在 while 迴圈裡面加上 sleep ,讓 run 方法放棄 cpu ,但是不放棄鎖,這個時候由於 CPU 有空閑的時候就去按照 JVM 的要求去保證記憶體的可見性。如下所示。  run 方法裡面休息了 3 秒,cpu 有充足的空閑時間去取變數的最新值,所以迴圈執行一次就停止了。

  1. public class StopThread {

  2.    private static boolean stopRequested;

  3.    public static void main(String[] args) throws InterruptedException {

  4.        Thread backgroundThread = new Thread(new Runnable() {

  5.            @Override

  6.            public void run() {

  7.                int i = 0;

  8.                while (!stopRequested) {

  9.                    try {

  10.                        TimeUnit.SECONDS.sleep(3);

  11.                    } catch (InterruptedException e) {

  12.                        // TODO Auto-generated catch block

  13.                        e.printStackTrace();

  14.                    }

  15.                    System.out.println(i);

  16.                }

  17.            }

  18.        });

  19.        backgroundThread.start();

  20.        TimeUnit.SECONDS.sleep(1);

  21.        stopRequested = true;

  22.    }

  23. }

快速排序演算法到底有多快?

微服務實戰:使用API Gateway

MySQL的索引是什麼?怎麼優化?

在一個千萬級的資料庫查尋中,如何提高查詢效率?

你真的瞭解try{ return }finally{}中的return?

推薦四十多條純乾貨 Java 代碼優化建議

一個致命的 Redis 命令,導致公司損失 400 萬!!

美團三面:一個執行緒OOM,行程里其他執行緒還能運行麽?

設計樣式六大原則,你真的懂了嗎?

6 個實體詳解如何把 if-else 代碼重構成高質量代碼

END


>>>>>> 加群交流技術 <<<<<<

赞(0)

分享創造快樂

© 2021 知識星球   网站地图