join和wait

最近看多線程的時候發現對於join的理解有些錯誤,在網上查了很多資料,根據本身的理解整理了一下,這裏之因此把join和wait放在一塊兒,是由於join的底層實現就是基於wait的,一併講解更容易理解。多線程

wait

瞭解join就先須要瞭解wait,wait是線程間通訊經常使用的信號量,做用就是讓線程暫時中止運行,等待其餘線程使用notify來喚醒或者達到必定條件本身甦醒。
wait是一個本地方法,屬於Object類,其底層實現是JVM內部實現,是基於monitor對象監視鎖。ide

//本地方法
public final native void wait(long timeout) throws InterruptedException;
//參數有納秒和毫秒
public final void wait(long timeout, int nanos) throws InterruptedException {
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }
    //存在納秒,默認加一毫秒
    if (nanos > 0) {
        timeout++;
    }

    wait(timeout);
}
//無參數,默認爲0
public final void wait() throws InterruptedException {
    wait(0);
}

根據源碼能夠發現,雖然wait有三個重載的方法,可是主要的仍是wait(long timeout)這個本地方法,其餘兩個都是基於這個來封裝的,由JVM底層源碼不太好看到,我就以流程的形式來描述。this

synchronized (this) {
        System.out.println("A begin " + System.currentTimeMillis());
        try {
            wait(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("A end " + System.currentTimeMillis());
}
  • 上述代碼在執行到wait(5000)時首先會釋放當前佔用的鎖,並暫停線程。
  • 在暫停的5秒內若是收到其餘線程的notify()方法發來的信號,那麼就再次嘗試獲取已經釋放的鎖
  • 若是獲取到那麼就繼續執行,沒有就等待鎖釋放來競爭。
  • 若是在5秒內未收到信號,那麼到時間後就自動甦醒去嘗試獲取鎖。

而對於時間的參數timeout須要注意的是,若是輸入0不表明不暫停,而是須要特殊狀況本身甦醒或者notify喚醒,這裏有個特殊點,wait(0)是能夠本身甦醒的線程

public class Thread2 extends Thread{
    private Thread1 a;
    public Thread2(Thread1 a) {
        this.a = a;
    }

    @Override
    public void run() {
        synchronized (a) {
            System.out.println("B begin " + System.currentTimeMillis());
            try {
            a.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B end " + System.currentTimeMillis());
        }
    }
}

public class Thread1 extends Thread{
    @Override
    public void run() {
        synchronized (this) {
            System.out.println("A begin " + System.currentTimeMillis());
            System.out.println("A end " + System.currentTimeMillis());
        }
    }
}

public class Main{
    public static void main(String[] args) {
        Thread1 thread = new Thread1();
        Thread2 thread2 = new Thread2(thread);
        thread2.start();
        thread.start();
        System.out.println("main end "+System.currentTimeMillis());
    }
}

這個例子運行結果存在如下狀況code

B begin 1494995803564
main end 1494995803565
A begin 1494995803565
A end 1494995803565
B end 1494995803565

wait()在沒有notify()狀況下自動甦醒了,所以這裏能夠看到,當前狀況下Thread.wait()等待過程當中,若是Thread結束了,是能夠自動喚醒的。這個會在join中被使用。對象

join

瞭解了wait的實現原理以後就能夠來看join了,join是Thread類的方法,不是底層本地方法,這裏能夠看一下它的源碼。同步

public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    //參數判斷<0,拋異常
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    //參數爲0,即join()
    if (millis == 0) {
        //當前線程存活,就調用wait(0),一直到調用join的線程結束再自動甦醒
        while (isAlive()) {
            wait(0);
        }
        //參數>0,調用wait(long millis)等待一段時間後自動喚醒
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

public final synchronized void join(long millis, int nanos)
throws InterruptedException {

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }

    join(millis);
}

public final void join() throws InterruptedException {
    join(0);
}

很明顯,join的三個重載方法主要仍是基於join(long millis)方法,所以咱們主要關注這個方法,方法的處理邏輯以下源碼

  • 判斷參數時間參數,若是參數小於0,拋出IllegalArgumentException("timeout value is negative")異常
  • 參數等於0,判斷調用join的線程(假設是A)是否存活,不存活就不執行操做,若是存活,就調用wait(0),阻塞join方法,等待A線程執行完在結束join方法。
  • 參數大於0,判斷調用join的A線程是否存活,不存活就不執行操做,若是存活,就調用wait(long millis),阻塞join方法,等待時間結束再繼續執行join方法。

因爲join是synchronized修飾的同步方法,所以會出現join(long millis)阻塞時間超過了millis的值。it

public class Thread1 extends Thread{
    @Override
    public void run() {
        synchronized (this) {
            System.out.println("A begin " + System.currentTimeMillis());
            try {
                sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("A end " + System.currentTimeMillis());
        }
    }
}

public class Main{
    public static void main(String[] args) {
        Thread1 thread = new Thread1();
        thread.start();
        try {
            thread.join(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        System.out.println("main end "+System.currentTimeMillis());
    }
}

這個例子的運行結果是io

A begin 1494996862054
A end 1494996867056
main end 1494996867056

main線程必定是最後執行完的,按照流程來講,1秒以後阻塞就結束了,main線程應該就能夠開始執行了,可是這裏有一個注意點,join(long millis)在執行millis>0的時候在wait(delay)以後還有一行代碼,而上面代碼1秒以後只是結束了wait方法,並無執行完join方法。上面的例子,因爲join的鎖和thread的鎖相同,在thread運行完以前,鎖不會釋放,那麼致使join一直阻塞在最後一步沒法結束,纔會出現上面的狀況。

相關文章
相關標籤/搜索