方法join()使用詳解

  在線程的常見方法一節中,已經接觸過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;
            }
        }
    }

 

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();
    }
}

 

結果: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關鍵字使用的是"對象監視器"原理做爲同步。對象

 

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 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

相關文章
相關標籤/搜索