join和wait


最近看多線程的時候發現對於join的理解有些錯誤,在網上查了不少資料,根據自己的理解整理了一下,這里之所以把join和wait放在一起,是因為join的底層實現就是基於wait的,一並講解更容易理解。

wait

了解join就先需要了解wait,wait是線程間通信常用的信號量,作用就是讓線程暫時停止運行,等待其他線程使用notify來喚醒或者達到一定條件自己蘇醒。
wait是一個本地方法,屬於Object類,其底層實現是JVM內部實現,是基於monitor對象監視鎖。

//本地方法
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底層源碼不太好看到,我就以流程的形式來描述。

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

這個例子運行結果存在以下情況

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的值。

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

這個例子的運行結果是

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一直阻塞在最后一步無法結束,才會出現上面的情況。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM