多線程:爲什麼在while循環中加入System.out.println,線程可以停止

在論壇看到這樣一個代碼:
public class StopThread {    private static boolean stopRequested;    public static void main(String[] args) throws InterruptedException {        Thread backgroundThread = new Thread(new Runnable() {            @Override            public void run() {                int i = 0;                while (!stopRequested) {                    i++;                }            }        });        backgroundThread.start();        TimeUnit.SECONDS.sleep(1);        stopRequested = true;    }}

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

如下就能停止線程的運行:

public class StopThread {    private static boolean stopRequested;    public static void main(String[] args) throws InterruptedException {        Thread backgroundThread = new Thread(new Runnable() {            @Override            public void run() {                int i = 0;                while (!stopRequested) {                    i++;                    System.out.println(""+i);                }            }        });        backgroundThread.start();         TimeUnit.SECONDS.sleep(1);        stopRequested = true;    }}

如上面所示,加了 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 有充足的空閒時間去取變量的最新值,所以循環執行一次就停止了。

public class StopThread {    private static boolean stopRequested;    public static void main(String[] args) throws InterruptedException {        Thread backgroundThread = new Thread(new Runnable() {            @Override            public void run() {                int i = 0;                while (!stopRequested) {                    try {                        TimeUnit.SECONDS.sleep(3);                    } catch (InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                    System.out.println(i);                }            }        });        backgroundThread.start();        TimeUnit.SECONDS.sleep(1);        stopRequested = true;    }}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章