在線程的常見方法一節中,已經接觸過join()方法的使用。
在很多情況下,主線程創建並啟動子線程,如果子線程中要進行大量的耗時運算,主線程將早於子線程結束。這時,如果主線程想等子線程執行完成才結束,比如子線程處理一個數據,主線程想要獲得這個數據中的值,就要用到join()方法了。方法join()的作用是等待線程對象銷毀。
join方法的主要作用就是同步,它可以使得線程之間的並行執行變為串行執行。在A線程中調用了B線程的join()方法時,表示只有當B線程執行完畢時,A線程才能繼續執行。
join方法中如果傳入參數,則表示這樣的意思:如果A線程中掉用B線程的join(10),則表示A線程會等待B線程執行10毫秒,10毫秒過后,A、B線程並行執行。需要注意的是,jdk規定,join(0)的意思不是A線程等待B線程0秒,而是A線程等待B線程無限時間,直到B線程執行完畢,即join(0)等價於join()。(其實join()中調用的是join(0))
join方法必須在線程start方法調用之后調用才有意義。這個也很容易理解:如果一個線程都沒有start,那它也就無法同步了。
源碼如下: 方法join(long)的功能在內部是使用wait(long)來實現的,所以join(long)方法具有釋放鎖的特點。
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; } } }
1.一個簡單的join方法的使用方法
package cn.qlq.thread.nine; /** * 線程類join()使用方法 * * @author Administrator * */ public class Demo1 extends Thread { /** * 更改線程名字 * * @param threadName */ public Demo1(String threadName) { this.setName(threadName); } @Override public void run() { for (int i = 0; i < 2; i++) { try { Thread.sleep(1 * 500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "-----" + i); } } public static void main(String[] args) { Demo1 t1 = new Demo1("t1"); Demo1 t2 = new Demo1("t2"); Demo1 t3 = new Demo1("t3"); t1.start(); /** * join的意思是使得放棄當前線程的執行,並返回對應的線程,例如下面代碼的意思就是: * 程序在main線程中調用t1線程的join方法,則main線程放棄cpu控制權,並返回t1線程繼續執行直到線程t1執行完畢 * 所以結果是t1線程執行完后,才到主線程執行,相當於在main線程中同步t1線程,t1執行完了,main線程才有執行的機會 */ try { t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } if (t2.isAlive()) { System.out.println("t2 is alive"); } else { System.out.println("t2 is not alive"); } t2.start(); t3.start(); } }
結果:
t1-----0
t1-----1
t2 is not alive
t3-----0
t2-----0
t2-----1
t3-----1
方法x.join()的作用是使所屬線程x 正常執行run()中的方法,而使得調用x.join()的線程處於無限期阻塞狀態,等待x線程銷毀后再繼續執行線程z后面的代碼。
方法join()具有使線程排隊運行的作用,有些類似於同步的運行效果。join()與synchronized的區別是:join在內部調用wait()方法進行等待,而synchronized關鍵字使用的是"對象監視器"原理作為同步。
2. join()與異常
在join()過程中,如果當前線程被中斷,則當前線程出現異常。(注意是調用thread.join()的線程被中斷才會進入異常,比如a線程調用b.join(),a中斷會報異常而b中斷不會異常)
如下:threadB中啟動threadA,並且調用其方法等待threadA完成,此時向threadB發出中斷信號,會進入中斷異常代碼。
package cn.qlq.thread.nine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 線程類join()使用方法--join中中斷 * * @author Administrator * */ public class Demo2 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo2.class); public static void main(String[] args) throws InterruptedException { final Thread threadA = new Thread(new Runnable() { @Override public void run() { LOGGER.info("threadA run"); while (true) { } } }, "threadA"); Thread threadB = new Thread(new Runnable() { @Override public void run() { LOGGER.info("threadB run"); threadA.start(); try { threadA.join(); } catch (InterruptedException e) { LOGGER.error("join error ,threadName - > {}", Thread.currentThread().getName(), e); } } }, "threadB"); threadB.start(); // 向threadB發出中斷信號 Thread.sleep(1 * 1000); threadB.interrupt(); } }
結果:
上面雖然進入異常代碼塊,但是線程仍然未停止 (因為threadA並沒有拋出異常,所以仍然在存活),我們用jvisualVM查看線程:
3. 方法join(long)的使用
方法join(long)是設定等待的時間。實際join()方法中調用的是join(0),當參數是0的時候表示無限期等待。
package cn.qlq.thread.nine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 線程類join()使用方法--join中中斷 * * @author Administrator * */ public class Demo3 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo3.class); public static void main(String[] args) throws InterruptedException { final Thread threadA = new Thread(new Runnable() { @Override public void run() { LOGGER.info("threadA run"); while (true) { } } }, "threadA"); Thread threadB = new Thread(new Runnable() { @Override public void run() { LOGGER.info("threadB run"); threadA.start(); try { threadA.join(2 * 1000); } catch (InterruptedException e) { LOGGER.error("join error ,threadName - > {}", Thread.currentThread().getName(), e); } LOGGER.info("threadB end"); } }, "threadB"); threadB.start(); } }
結果:(threadB線程等待threadA線程2秒鍾之后兩個線程開始並行運行)
4. 方法join(long)與sleep(long)的區別
方法join(long)的功能在內部是使用wait(long)來實現的,所以join(long)方法具有釋放鎖的特點。
方法join(long)的源碼如下:
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(long)方法之后內部調用了wait()方法,因此會釋放該對象鎖。
(1)測試join(long)會釋放鎖
package cn.qlq.thread.nine; import org.slf4j.Logger; import org.slf4j.LoggerFactory;/** * join(long)釋放鎖 * * @author Administrator * */ public class Demo4 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo4.class); public static void main(String[] args) throws InterruptedException { LOGGER.info("main start"); final Demo4 demo4 = new Demo4(); // 啟動demo4線程並且占用鎖之后調用join(long)方法 new Thread(new Runnable() { @Override public void run() { try { synchronized (demo4) { LOGGER.info("進入同步代碼塊,threadName ->{} 占有 demo4 的鎖", Thread.currentThread().getName()); demo4.start(); demo4.join(4 * 1000); LOGGER.info("退出同步代碼塊,threadName ->{}", Thread.currentThread().getName()); } } catch (InterruptedException e) { e.printStackTrace(); } } }, "threadA").start(); // 休眠2秒鍾,調用對象的同步方法 Thread.currentThread().sleep(2 * 1000); demo4.test2(); } @Override public void run() { try { Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void test2() { LOGGER.info("進入test2方法,占有鎖,threadname->{}", currentThread().getName()); } }
結果: (在休眠2秒鍾后調用對象的同步方法能進入方法則證明join方法釋放鎖;而且在退出同步代碼塊之前打印了test信息則說明test2占用鎖成功)
17:57:02 [cn.qlq.thread.nine.Demo4]-[INFO] main start 17:57:02 [cn.qlq.thread.nine.Demo4]-[INFO] 進入同步代碼塊,threadName ->threadA 占有 demo4 的鎖 17:57:04 [cn.qlq.thread.nine.Demo4]-[INFO] 進入test2方法,占有鎖,threadname->main 17:57:06 [cn.qlq.thread.nine.Demo4]-[INFO] 退出同步代碼塊,threadName ->threadA
(2)測試sleep(long)不會釋放鎖
package cn.qlq.thread.nine; import org.slf4j.Logger; import org.slf4j.LoggerFactory;/** * sleep(long)不會釋放鎖 * * @author Administrator * */ public class Demo5 extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(Demo5.class); public static void main(String[] args) throws InterruptedException { LOGGER.info("main start"); final Demo5 demo4 = new Demo5(); // 啟動demo4線程並且占用鎖之后調用join(long)方法 new Thread(new Runnable() { @Override public void run() { try { synchronized (demo4) { LOGGER.info("進入同步代碼塊,threadName ->{} 占有 demo4 的鎖", Thread.currentThread().getName()); demo4.start(); demo4.sleep(4 * 1000); LOGGER.info("退出同步代碼塊,threadName ->{}", Thread.currentThread().getName()); } } catch (InterruptedException e) { e.printStackTrace(); } } }, "threadA").start(); // 休眠2秒鍾,調用對象的同步方法 Thread.currentThread().sleep(2 * 1000); demo4.test2(); } @Override public void run() { try { Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void test2() { LOGGER.info("進入test2方法,占有鎖,threadname->{}", currentThread().getName()); } }
結果:(退出代碼塊才進入test2方法,證明sleep(long)沒有釋放鎖)
17:59:30 [cn.qlq.thread.nine.Demo5]-[INFO] main start
17:59:30 [cn.qlq.thread.nine.Demo5]-[INFO] 進入同步代碼塊,threadName ->threadA 占有 demo4 的鎖
17:59:34 [cn.qlq.thread.nine.Demo5]-[INFO] 退出同步代碼塊,threadName ->threadA
17:59:34 [cn.qlq.thread.nine.Demo5]-[INFO] 進入test2方法,占有鎖,threadname->main