問題來自於學習thinking in java的時候的一個示例,先上代碼吧
public class StopThread {
private static boolean stop = false; public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Runnable(){ public void run() { int i = 0; while (!stop) { i++; } } }); t.start(); TimeUnit.SECONDS.sleep(1); stop = true; } }
毫無疑問,這段代碼會永遠的執行下去,因為后台線程感覺不到主線程已經改變了stop,
然后我習慣性的在while循環中打印了下i(syso)
然后運行,發現程序在運行了一秒左右就停止了!!
我一臉懵逼,然后看了下syso的代碼,發現有一段同步塊
public void println(int x) {
synchronized (this) { print(x); newLine(); } }
然后我也在代碼里嘗試着加了一個空的同步塊,發現也會停止
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable(){ @Override public void run() { int i = 0; Object lock = new Object(); long start = System.nanoTime(); while (!stop) { i++; synchronized (lock) { // nothing } } long end = System.nanoTime(); Duration d = Duration.ofNanos(end - start); System.out.println(d.toMillis() + "ms"); } }); t.start(); TimeUnit.SECONDS.sleep(1); stop = true; }
運行結果是 1000ms
那么問題來了,synchronized 到底干了什么。。
按理說,synchronized 只會保證該同步塊中的變量的可見性,發生變化后立即同步到主存,但是,stop變量並不在同步塊中
實際上,JVM對於現代的機器做了最大程度的優化,也就是說,最大程度的保障了線程和主存之間的及時的同步,也就是相當於虛擬機盡可能的幫我們加了個volatile,但是,當CPU被一直占用的時候,同步就會出現不及時,也就出現了后台線程一直不結束的情況。
舉個例子,在while循環中sleep一下,程序會很快的結束,sleep方法中並沒有同步代碼塊
public void run() { int i = 0; long start = System.nanoTime(); while (!stop) { i++; try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { } } long end = System.nanoTime(); Duration d = Duration.ofNanos(end - start); System.out.println(d.toMillis() + "ms"); }
再舉個栗子,在while循環中做一些耗時但不耗CPU的操作,也會結束的很快,因為這個時候CPU空閑了,JVM就有機會盡快的將主存和棧變量同步
public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Runnable(){ @Override public void run() { int i = 0; long start = System.nanoTime(); while (!stop) { i++; Object[] arr = new Object[1000000]; } long end = System.nanoTime(); Duration d = Duration.ofNanos(end - start); System.out.println(d.toMillis() + "ms"); } }); t.start(); TimeUnit.SECONDS.sleep(1); stop = true; }