顛覆我的Thread.join()
學而時習之,不亦說乎!
--《論語》
為什麽說是顛覆?
1)任何對象都可以作為鎖對象,鎖對象的行為都是一樣的嗎?之前我一直認為鎖對象的方法都是定義在Object類中,而所有類都是Object的子類,這些方法又都是native方法,那麽用哪個對象作為鎖對象又有什麽區別呢?
2)一個線程對象a在run()方法內部調用線程對象b的join()方法,那麽是將b線程加入,等到b線程執行完畢再執行a線程?那麽如果還有一個正在執行的c線程呢,線程c也會等待b執行完嗎?
代碼1:
package com.zby; public classApplication1 { public static void main(String[] args) { Thread prepare = new Thread(new Runnable() { public void run() { for (int i = 0; i < 5; i++) { System.out.println("Hello,World!-----" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }); prepare.start(); System.out.println("Hello,ZBY!"); } }
控制臺輸出:
Hello,ZBY! Hello,World!-----0 Hello,World!-----1 Hello,World!-----2 Hello,World!-----3 Hello,World!-----4
結果不難分析,主線程直接執行,而prepare線程雖然啟動了,但是執行沒那麽快,所以後執行了。但是我的prepare是進行準備工作的,我想讓prepare線程執行完畢後再執行主線程。
代碼2:
package com.zby; public class Application2 { public static void main(String[] args) { Thread prepare = new Thread(new Runnable() { public void run() { for (int i = 0; i < 5; i++) { System.out.println("Hello,World!-----" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }); prepare.start(); try { prepare.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Hello,ZBY!"); } }
控制臺輸出:
Hello,World!-----0 Hello,World!-----1 Hello,World!-----2 Hello,World!-----3 Hello,World!-----4 Hello,ZBY!
很小兒科,加了一個一行代碼:prepare.join();要是之前我會理解成把prepare加入到主線程先執行,執行完才能執行其它線程。然而,非也。
Thread.join()源代碼:
public final void join() throws InterruptedException { join(0); } public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
這兒可以看出來,我們調用prepare.join()的時候發生了什麽:把prepare作為鎖對象,調用鎖對象的wait(0)方法,阻塞當前線程!也就是說,並不是把需要執行的線程加入進來讓他先執行,而是阻塞當前的線程!那麽,如果還有第三個線程也在執行,那麽prepare線程是不會一直執行,而是跟第三個線程搶CPU執行權。總結起來就是,其實就是阻塞了當前的線程,對於其他線程都是沒有影響的。感興趣可以加入第三個線程自己測試一下。
等價於代碼2的代碼3:
package com.zby; public class Application3 { public static void main(String[] args) { Thread prepare = new Thread(new Runnable() { public void run() { for (int i = 0; i < 5; i++) { System.out.println("Hello,World!-----" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }); prepare.start(); synchronized(prepare){ try { prepare.wait(0); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Hello,ZBY!"); } }
控制臺輸出:
Hello,World!-----0 Hello,World!-----1 Hello,World!-----2 Hello,World!-----3 Hello,World!-----4 Hello,ZBY!
這兒就解決了第二個問題,join()方法其實不是把調用的線程加入進來優先執行,而是阻塞當前線程!
看完代碼3就有疑問了,prepare.wait(0);自然沒錯,阻塞住了主線程。但是並沒有任何地方調用notify或者notifyAll方法,線程不是應該一直阻塞麽,怎麽會在prepare執行完後繼續執行主線程代碼了?
這就是第一個問題了,普通對象當然是wait後必須等待notify喚醒才能繼續執行,但是Thread對象呢?具體的我也不知道,但是從這兒可以推論出,thread對象在執行完畢後,自動喚醒了!那麽到底是notify還是notifyAll呢?那麽,多啟動一個線程,並使用prepare對象作為鎖對象,調用wait方法。
代碼4:
package com.zby; public class Application3 { public static void main(String[] args) { final Thread prepare = new Thread(new Runnable() { public void run() { for (int i = 0; i < 5; i++) { System.out.println("Hello,World!-----" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }); prepare.start(); Thread ready = new Thread(new Runnable() { public void run() { synchronized (prepare) { try { prepare.wait(0); } catch (InterruptedException e) { e.printStackTrace(); } } for (int i = 0; i < 10; i++) { System.out.println("Hello,Earth!-----" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }); ready.start(); synchronized (prepare) { try { prepare.wait(0); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Hello,ZBY!"); } }
控制臺輸出:
Hello,World!-----0 Hello,World!-----1 Hello,World!-----2 Hello,World!-----3 Hello,World!-----4 Hello,Earth!-----0 Hello,ZBY! Hello,Earth!-----1 Hello,Earth!-----2 Hello,Earth!-----3 Hello,Earth!-----4 Hello,Earth!-----5 Hello,Earth!-----6 Hello,Earth!-----7 Hello,Earth!-----8 Hello,Earth!-----9
可以看出,在preare執行完之後,自動把ready和main線程都喚醒了!也就是說,使用Thread對象作為鎖對象,在Thread執行完成之後,會喚醒使用該對象作為鎖對象調用wait()休眠的線程。
總結:
1)使用線程的join方法,是將調用的線程對象作為鎖對象,阻塞當前線程,不影響其他線程的運行。
2)推論:Thread對象作為線程鎖對象,會在Thread對象執行完後,調用Thread對象的notifyAll方法。
顛覆我的Thread.join()