1. 程式人生 > >java多執行緒中的join方法詳解

java多執行緒中的join方法詳解

 方法Join是幹啥用的? 簡單回答,同步,如何同步? 怎麼實現的? 下面將逐個回答。

    自從接觸Java多執行緒,一直對Join理解不了。JDK是這樣說的:join public final void join(long millis)throws InterruptedException Waits at most millis milliseconds for this thread to die. A timeout of 0 means to wait forever.大家能理解嗎? 字面意思是等待一段時間直到這個執行緒死亡,我的疑問是那個執行緒,是它本身的執行緒還是呼叫它的執行緒的,上程式碼: 

package concurrentstudy;
/**
 *
 * @author vma
 */
public class JoinTest {
    public static void main(String[] args) {
        Thread t = new Thread(new RunnableImpl());
        t.start();
        try {
            t.join(1000);
            System.out.println("joinFinish");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
     
        }
    }
}
class RunnableImpl implements Runnable {

    @Override
    public void run() {
        try {
            System.out.println("Begin sleep");
            Thread.sleep(1000);
           System.out.println("End sleep");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

結果是:
Begin sleep
End sleep
joinFinish
明白了吧,當main執行緒呼叫t.join時,main執行緒等待t執行緒,等待時間是1000,如果t執行緒Sleep 2000呢
 public void run() {
        try {
            System.out.println("Begin sleep");
            // Thread.sleep(1000);
            Thread.sleep(2000);
           System.out.println("End sleep");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
結果是:
Begin sleep
joinFinish
End sleep
也就是說main執行緒只等1000毫秒,不管T什麼時候結束,如果是t.join()呢, 看程式碼:  
 public final void join() throws InterruptedException {
    join(0);
    }
    就是說如果是t.join() = t.join(0) 0 JDK這樣說的 A timeout of 0

means to wait forever 字面意思是永遠等待,是這樣嗎?
    其實是等到t結束後。
    這個是怎麼實現的嗎? 看JDK程式碼:

    /**
     * Waits at most <code>millis</code> milliseconds for this thread to 
     * die. A timeout of <code>0</code> means to wait forever. 
     *
     * @param      millis   the time to wait in milliseconds.
     * @exception  InterruptedException if any thread has interrupted
     *             the current thread.  The <i>interrupted status</i> of the
     *             current thread is cleared when this exception is thrown.
     */
    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;
        }
    }
    }

    其實Join方法實現是通過wait(小提示:Object 提供的方法)。 當main執行緒呼叫t.join時候,main執行緒會獲得執行緒物件t的鎖(wait 意味著拿到該物件的鎖),呼叫該物件的wait(等待時間),直到該物件喚醒main執行緒,比如退出後。



    這就意味著main 執行緒呼叫t.join時,必須能夠拿到執行緒t物件的鎖,如果拿不到它是無法wait的,剛開的例子t.join(1000)不是說明了main執行緒等待1 秒,如果在它等待之前,其他執行緒獲取了t物件的鎖,它等待時間可不就是1毫秒了。上程式碼介紹:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package concurrentstudy;
/**
 *
 * @author vma
 */
public class JoinTest {
    public static void main(String[] args) {
        Thread t = new Thread(new RunnableImpl());
       new ThreadTest(t).start();
        t.start();
        try {
            t.join();
            System.out.println("joinFinish");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
     
        }
    }
}
class ThreadTest extends Thread {

    Thread thread;

    public ThreadTest(Thread thread) {
        this.thread = thread;
    }

    @Override
    public void run() {
        holdThreadLock();
    }

    public void holdThreadLock() {
        synchronized (thread) {
            System.out.println("getObjectLock");
            try {
                Thread.sleep(9000);

            } catch (InterruptedException ex) {
             ex.printStackTrace();
            }
            System.out.println("ReleaseObjectLock");
        }

    }
}

class RunnableImpl implements Runnable {

    @Override
    public void run() {
        try {
            System.out.println("Begin sleep");
            Thread.sleep(2000);
           System.out.println("End sleep");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }
}

    在main方法中 通過new ThreadTest(t).start();例項化ThreadTest 執行緒物件, 它在holdThreadLock()方法中,通過 synchronized (thread),獲取執行緒物件t的鎖,並Sleep(9000)後釋放,這就意味著,即使
main方法t.join(1000),等待一秒鐘,它必須等待ThreadTest 執行緒釋放t鎖後才能進入wait方法中,它實際等待時間是9000+1000 MS
執行結果是:
getObjectLock
Begin sleep
End sleep
ReleaseObjectLock
joinFinish

轉自:http://java.chinaitlab.com/JDK/760879.html

ps:

二、為什麼要用join()方法

主執行緒生成並起動了子執行緒,而子執行緒裡要進行大量的耗時的運算(這裡可以借鑑下執行緒的作用),當主執行緒處理完其他的事務後,需要用到子執行緒的處理結果,這個時候就要用到join();方法了。

三、join方法的作用

在網上看到有人說“將兩個執行緒合併”。這樣解釋我覺得理解起來還更麻煩。不如就借鑑下API裡的說法:

“等待該執行緒終止。”

解釋一下,是主執行緒(我在“一”裡已經命名過了)等待子執行緒的終止。也就是在子執行緒呼叫了join()方法後面的程式碼,只有等到子執行緒結束了才能執行。(Waits for this thread to die.)

四、用例項來理解

寫一個簡單的例子來看一下join()的用法,一共三個類:

1.CustomThread 類

2. CustomThread1類

3. JoinTestDemo 類,main方法所在的類。

程式碼1:

  1. package wxhx.csdn2;  
  2. /** 
  3.  *  
  4.  * @author bzwm 
  5.  * 
  6.  */  
  7. class CustomThread1 extends Thread {  
  8.     public CustomThread1() {  
  9.         super("[CustomThread1] Thread");  
  10.     };  
  11.     public void run() {  
  12.         String threadName = Thread.currentThread().getName();  
  13.         System.out.println(threadName + " start.");  
  14.         try {  
  15.             for (int i = 0; i < 5; i++) {  
  16.                 System.out.println(threadName + " loop at " + i);  
  17.                 Thread.sleep(1000);  
  18.             }  
  19.             System.out.println(threadName + " end.");  
  20.         } catch (Exception e) {  
  21.             System.out.println("Exception from " + threadName + ".run");  
  22.         }  
  23.     }  
  24. }  
  25. class CustomThread extends Thread {  
  26.     CustomThread1 t1;  
  27.     public CustomThread(CustomThread1 t1) {  
  28.         super("[CustomThread] Thread");  
  29.         this.t1 = t1;  
  30.     }  
  31.     public void run() {  
  32.         String threadName = Thread.currentThread().getName();  
  33.         System.out.println(threadName + " start.");  
  34.         try {  
  35.             t1.join();  
  36.             System.out.println(threadName + " end.");  
  37.         } catch (Exception e) {  
  38.             System.out.println("Exception from " + threadName + ".run");  
  39.         }  
  40.     }  
  41. }  
  42. public class JoinTestDemo {  
  43.     public static void main(String[] args) {  
  44.         String threadName = Thread.currentThread().getName();  
  45.         System.out.println(threadName + " start.");  
  46.         CustomThread1 t1 = new CustomThread1();  
  47.         CustomThread t = new CustomThread(t1);  
  48.         try {  
  49.             t1.start();  
  50.             Thread.sleep(2000);  
  51.             t.start();  
  52.             t.join();//在程式碼2裡,將此處注釋掉  
  53.         } catch (Exception e) {  
  54.             System.out.println("Exception from main");  
  55.         }  
  56.         System.out.println(threadName + " end!");  
  57.     }  
  58. }  

列印結果:

main start.//main方法所在的執行緒起動,但沒有馬上結束,因為呼叫t.join();,所以要等到t結束了,此執行緒才能向下執行。

[CustomThread1] Thread start.//執行緒CustomThread1起動

[CustomThread1] Thread loop at 0//執行緒CustomThread1執行

[CustomThread1] Thread loop at 1//執行緒CustomThread1執行

[CustomThread] Thread start.//執行緒CustomThread起動,但沒有馬上結束,因為呼叫t1.join();,所以要等到t1結束了,此執行緒才能向下執行。

[CustomThread1] Thread loop at 2//執行緒CustomThread1繼續執行

[CustomThread1] Thread loop at 3//執行緒CustomThread1繼續執行

[CustomThread1] Thread loop at 4//執行緒CustomThread1繼續執行

[CustomThread1] Thread end. //執行緒CustomThread1結束了

[CustomThread] Thread end.// 執行緒CustomThread在t1.join();阻塞處起動,向下繼續執行的結果

main end!//執行緒CustomThread結束,此執行緒在t.join();阻塞處起動,向下繼續執行的結果。

修改一下程式碼,得到程式碼2:(這裡只寫出修改的部分)

  1. public class JoinTestDemo {  
  2.     public static void main(String[] args) {  
  3.         String threadName = Thread.currentThread().getName();  
  4.         System.out.println(threadName + " start.");  
  5.         CustomThread1 t1 = new CustomThread1();  
  6.         CustomThread t = new CustomThread(t1);  
  7.         try {  
  8.             t1.start();  
  9.             Thread.sleep(2000);  
  10.             t.start();  
  11. //          t.join();//在程式碼2裡,將此處注釋掉  
  12.         } catch (Exception e) {  
  13.             System.out.println("Exception from main");  
  14.         }  
  15.         System.out.println(threadName + " end!");  
  16.     }  
  17. }  

列印結果:

main start. // main方法所在的執行緒起動,但沒有馬上結束,這裡並不是因為join方法,而是因為Thread.sleep(2000);

[CustomThread1] Thread start. //執行緒CustomThread1起動

[CustomThread1] Thread loop at 0//執行緒CustomThread1執行

[CustomThread1] Thread loop at 1//執行緒CustomThread1執行

main end!// Thread.sleep(2000);結束,雖然線上程CustomThread執行了t1.join();,但這並不會影響到其他執行緒(這裡main方法所在的執行緒)。

[CustomThread] Thread start. //執行緒CustomThread起動,但沒有馬上結束,因為呼叫t1.join();,所以要等到t1結束了,此執行緒才能向下執行。

[CustomThread1] Thread loop at 2//執行緒CustomThread1繼續執行

[CustomThread1] Thread loop at 3//執行緒CustomThread1繼續執行

[CustomThread1] Thread loop at 4//執行緒CustomThread1繼續執行

[CustomThread1] Thread end. //執行緒CustomThread1結束了

[CustomThread] Thread end. // 執行緒CustomThread在t1.join();阻塞處起動,向下繼續執行的結果

五、從原始碼看join()方法

在CustomThread的run方法裡,執行了t1.join();,進入看一下它的JDK原始碼:

  1. public final void join() throws InterruptedException {  
  2. n(0);  
  3. }  

然後進入join(0)方法:

  1.    /** 
  2.     * Waits at most <code>millis</code> milliseconds for this thread to  
  3.     * die. A timeout of <code>0</code> means to wait forever. //注意這句 
  4.     * 
  5.     * @param      millis   the time to wait in milliseconds. 
  6.     * @exception  InterruptedException if another thread has interrupted 
  7.     *             the current thread.  The <i>interrupted status</i> of the 
  8.     *             current thread is cleared when this exception is thrown. 
  9.     */  
  10.    public final synchronized void join(long millis) //引數millis為0.  
  11.    throws InterruptedException {  
  12. long base = System.currentTimeMillis();  
  13. long now = 0;  
  14. if (millis < 0) {  
  15.            throw new IllegalArgumentException("timeout value is negative");  
  16. }  
  17. if (millis == 0) {//進入這個分支  
  18.     while (isAlive()) {//判斷本執行緒是否為活動的。這裡的本執行緒就是t1.  
  19.     wait(0);//阻塞  
  20.     }  
  21. } else {  
  22.     while (isAlive()) {  
  23.     long delay = millis - now;  
  24.     if (delay <= 0) {  
  25.         break;  
  26.     }  
  27.     wait(delay);  
  28.     now = System.currentTimeMillis() - base;  
  29.     }  
  30. }  
  31.    }  

單純從程式碼上看,如果執行緒被生成了,但還未被起動,呼叫它的join()方法是沒有作用的。將直接繼續向下執行,這裡就不寫程式碼驗證了。