在線程的常見方法一節中,已經接觸過join()方法的使用。html
在不少狀況下,主線程建立並啓動子線程,若是子線程中要進行大量的耗時運算,主線程將早於子線程結束。這時,若是主線程想等子線程執行完成才結束,好比子線程處理一個數據,主線程想要得到這個數據中的值,就要用到join()方法了。方法join()的做用是等待線程對象銷燬。ide
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))this
join方法必須在線程start方法調用以後調用纔有意義。這個也很容易理解:若是一個線程都沒有start,那它也就沒法同步了。spa
源碼以下: 方法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; } } }
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(); } }
結果:3d
t1-----0
t1-----1
t2 is not alive
t3-----0
t2-----0
t2-----1
t3-----1code
方法x.join()的做用是使所屬線程x 正常執行run()中的方法,而使得調用x.join()的線程處於無限期阻塞狀態,等待x線程銷燬後再繼續執行線程z後面的代碼。htm
方法join()具備使線程排隊運行的做用,有些相似於同步的運行效果。join()與synchronized的區別是:join在內部調用wait()方法進行等待,而synchronized關鍵字使用的是"對象監視器"原理做爲同步。對象
在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查看線程:
方法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秒鐘以後兩個線程開始並行運行)
方法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()方法,所以會釋放該對象鎖。
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 start17:59:30 [cn.qlq.thread.nine.Demo5]-[INFO] 進入同步代碼塊,threadName ->threadA 佔有 demo4 的鎖17:59:34 [cn.qlq.thread.nine.Demo5]-[INFO] 退出同步代碼塊,threadName ->threadA17:59:34 [cn.qlq.thread.nine.Demo5]-[INFO] 進入test2方法,佔有鎖,threadname->main