BAT面試題:現在有T1、T2、T3三個線程,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行? 這個線程問題通常會在第一輪或電話面試階段被問到,目的是檢測你對”join”方法是否熟悉。這個多線程問題比較簡單,可以用join方法實現。 一、作用 Thread類中的join方法的主要作用就是同 ...
BAT面試題:現在有T1、T2、T3三個線程,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行?
這個線程問題通常會在第一輪或電話面試階段被問到,目的是檢測你對”join”方法是否熟悉。這個多線程問題比較簡單,可以用join方法實現。
一、作用
Thread類中的join方法的主要作用就是同步,它可以使得線程之間的並行執行變為串列執行。具體看代碼:
1 public class JoinTest { 2 public static void main(String [] args) throws InterruptedException { 3 ThreadJoinTest t1 = new ThreadJoinTest("小明"); 4 ThreadJoinTest t2 = new ThreadJoinTest("小東"); 5 t1.start(); 6 /**join的意思是使得放棄當前線程的執行,並返回對應的線程,例如下麵代碼的意思就是: 7 程式在main線程中調用t1線程的join方法,則main線程放棄cpu控制權,並返回t1線程繼續執行直到線程t1執行完畢 8 所以結果是t1線程執行完後,才到主線程執行,相當於在main線程中同步t1線程,t1執行完了,main線程才有執行的機會 9 */ 10 t1.join(); 11 t2.start(); 12 } 13 14 } 15 class ThreadJoinTest extends Thread{ 16 public ThreadJoinTest(String name){ 17 super(name); 18 } 19 @Override 20 public void run(){ 21 for(int i=0;i<1000;i++){ 22 System.out.println(this.getName() + ":" + i); 23 } 24 } 25 }
上面程式結果是先列印完小明線程,在列印小東線程;
上面註釋也大概說明瞭join方法的作用:在A線程中調用了B線程的join()方法時,表示只有當B線程執行完畢時,A線程才能繼續執行。註意,這裡調用的join方法是沒有傳參的,join方法其實也可以傳遞一個參數給它的,具體看下麵的簡單例子:
1 public class JoinTest { 2 public static void main(String [] args) throws InterruptedException { 3 ThreadJoinTest t1 = new ThreadJoinTest("小明"); 4 ThreadJoinTest t2 = new ThreadJoinTest("小東"); 5 t1.start(); 6 /**join方法可以傳遞參數,join(10)表示main線程會等待t1線程10毫秒,10毫秒過去後, 7 * main線程和t1線程之間執行順序由串列執行變為普通的並行執行 8 */ 9 t1.join(10); 10 t2.start(); 11 } 12 13 } 14 class ThreadJoinTest extends Thread{ 15 public ThreadJoinTest(String name){ 16 super(name); 17 } 18 @Override 19 public void run(){ 20 for(int i=0;i<1000;i++){ 21 System.out.println(this.getName() + ":" + i); 22 } 23 } 24 }
上面代碼結果是:程式執行前面10毫秒內列印的都是小明線程,10毫秒後,小明和小東程式交替列印。
所以,join方法中如果傳入參數,則表示這樣的意思:如果A線程中掉用B線程的join(10),則表示A線程會等待B線程執行10毫秒,10毫秒過後,A、B線程並行執行。需要註意的是,jdk規定,join(0)的意思不是A線程等待B線程0秒,而是A線程等待B線程無限時間,直到B線程執行完畢,即join(0)等價於join()。
二、join與start調用順序問題
上面的討論大概知道了join的作用了,那麼,如果 join在start前調用,會出現什麼後果呢?先看下麵的測試結果
1 public class JoinTest { 2 public static void main(String [] args) throws InterruptedException { 3 ThreadJoinTest t1 = new ThreadJoinTest("小明"); 4 ThreadJoinTest t2 = new ThreadJoinTest("小東"); 5 /**join方法可以在start方法前調用時,並不能起到同步的作用 6 */ 7 t1.join(); 8 t1.start(); 9 //Thread.yield(); 10 t2.start(); 11 } 12 13 } 14 class ThreadJoinTest extends Thread{ 15 public ThreadJoinTest(String name){ 16 super(name); 17 } 18 @Override 19 public void run(){ 20 for(int i=0;i<1000;i++){ 21 System.out.println(this.getName() + ":" + i); 22 } 23 } 24 }
上面代碼執行結果是:小明和小東線程交替列印。
所以得到以下結論:join方法必須線上程start方法調用之後調用才有意義。這個也很容易理解:如果一個線程都沒有start,那它也就無法同步了。
三、join方法實現原理
有了上面的例子,我們大概知道join方法的作用了,那麼,join方法實現的原理是什麼呢?
其實,join方法是通過調用線程的wait方法來達到同步的目的的。例如,A線程中調用了B線程的join方法,則相當於A線程調用了B線程的wait方法,在調用了B線程的wait方法後,A線程就會進入阻塞狀態,具體看下麵的源碼:
1 public final synchronized void join(long millis) 2 throws InterruptedException { 3 long base = System.currentTimeMillis(); 4 long now = 0; 5 6 if (millis < 0) { 7 throw new IllegalArgumentException("timeout value is negative"); 8 } 9 10 if (millis == 0) { 11 while (isAlive()) { 12 wait(0); 13 } 14 } else { 15 while (isAlive()) { 16 long delay = millis - now; 17 if (delay <= 0) { 18 break; 19 } 20 wait(delay); 21 now = System.currentTimeMillis() - base; 22 } 23 } 24 }
從源碼中可以看到:join方法的原理就是調用相應線程的wait方法進行等待操作的,例如A線程中調用了B線程的join方法,則相當於在A線程中調用了B線程的wait方法,當B線程執行完(或者到達等待時間),B線程會自動調用自身的notifyAll方法喚醒A線程,從而達到同步的目的。