1. 程式人生 > >Java多執行緒初探——yield()方法與join()方法

Java多執行緒初探——yield()方法與join()方法

一、執行緒與程序

1、程序是程式(任務)執行過程,持有資源(共享記憶體,共享檔案)和執行緒,程序是動態性的,如果程式沒有執行就不算一個程序。
2、執行緒是系統中最小的執行單元,同一程序中有多個執行緒,執行緒共享程序的資源
Java中建立現成的方式就不再贅述了,有兩種:(1)繼承Thread類,重寫run()方法,(2)實現Runnable介面,重寫run()方法。

二、yield()方法

在jdk文件中,yield()方法是這樣描述的:暫停當前正在執行的執行緒物件,並執行其他執行緒。在多執行緒的情況下,由CPU決定執行哪一個執行緒,而yield()方法就是暫停當前的執行緒,讓給其他執行緒(包括它自己)執行,具體由誰執行由CPU決定。

yield()方法例項

YieldRunnable.java

public class YieldRunnable implements Runnable{
	public volatile boolean isRunning = true;
	
	@Override
	public void run() {
		String name = Thread.currentThread().getName();
		System.out.println(name + "開始執行!");
		
		while(isRunning) {
			for(int i = 1; i < 6; i++) {
				System.out.println(name + "執行了[" + i + "]次");
				//注意,yield是靜態方法
				Thread.yield();
			}
		}
		
		System.out.println(name + "執行結束!");
	}
}
執行的main函式
	public static void main(String[] args) {
		YieldRunnable runnable1 = new YieldRunnable();
		YieldRunnable runnable2 = new YieldRunnable();
		Thread thread1 = new Thread(runnable1, "執行緒1");
		Thread thread2 = new Thread(runnable2, "執行緒2"); 
		
		System.out.println("兩個執行緒準備開始執行");
		thread1.start();
		thread2.start();
		
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		runnable1.isRunning = false;
		runnable2.isRunning = false;
	}
選取部分執行結果分析:

1、第一種情況:

……

執行緒1執行了[1]次
執行緒2執行了[1]次
執行緒2執行了[2]次
執行緒2執行了[3]次
執行緒2執行了[4]次
執行緒1執行了[2]次

……
從這個執行結果可以看出,執行緒1在執行了一次之後,讓出執行權,CPU將執行權給了執行緒2,執行緒2執行了一次之後讓出執行權,但是CPU仍將執行權給2,就這樣執行緒2反覆執行了幾次,CPU一直都是將執行權給執行緒2,知道執行緒2執行完第4次之後,CPU才將執行權給執行緒1。

2、第二種情況:

……

執行緒1執行了[1]次
執行緒2執行了[3]次
執行緒1執行了[2]次
執行緒2執行了[4]次
執行緒1執行了[3]次
執行緒2執行了[5]次

……

從這個執行結果可以看出,執行緒1執行一次後,讓出執行權,CPU將執行權給了執行緒2,執行緒2執行一次後,CPU將執行權又給了1,就這樣交替執行了幾次執行緒1和執行緒2。

從上面兩種執行結果來看,一個執行緒執行yield()方法暫停後,CPU決定接下來有哪一個執行緒執行,可以是其他執行緒,也可以是原來的那個執行緒。

三、join()方法

join()方法是指等待呼叫join()方法的執行緒執行結束,程式才會繼續執行下去,這個方法適用於:一個執行程式必須等待另一個執行緒的執行結果才能夠繼續執行的情況。

join()方法例項

定義一個執行緒: JoinRunnable.java
public class JoinRunnable implements Runnable {
	@Override
	public void run() {
		String name = Thread.currentThread().getName();
		System.out.println(name + "開始執行!");
		
		for(int i = 1; i < 6; i++) {
			System.out.println(name + "執行了[" + i + "]次");
		}
	}
}
先來看一下沒有使用join()方法的情況:
	public static void main(String[] args) {
		JoinRunnable runnable1 = new JoinRunnable();
		Thread thread1 = new Thread(runnable1, "執行緒1");

		System.out.println("主執行緒開始執行!");
		thread1.start();

		System.out.println("主執行緒執行結束!");
	}
執行結果: 主執行緒開始執行!
主執行緒執行結束!
執行緒1開始執行!
執行緒1執行了[1]次
執行緒1執行了[2]次
執行緒1執行了[3]次
執行緒1執行了[4]次
執行緒1執行了[5]次
從執行結果可以看出,如果沒有使用join方法,那麼main方法所在的執行緒(暫定叫主執行緒)在啟動了JoinRunnable執行緒(暫定叫子執行緒)之後,沒有等待子執行緒執行結束,就先執行結束了。 如果使用了join方法:
	public static void main(String[] args) {
		JoinRunnable runnable1 = new JoinRunnable();
		Thread thread1 = new Thread(runnable1, "執行緒1");

		System.out.println("主執行緒開始執行!");
		thread1.start();
		
		try {
			thread1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("主執行緒執行結束!");
	}
執行結果: 主執行緒開始執行!
執行緒1開始執行!
執行緒1執行了[1]次
執行緒1執行了[2]次
執行緒1執行了[3]次
執行緒1執行了[4]次
執行緒1執行了[5]次
主執行緒執行結束! 從執行結果可以看出,加入join()方法,主執行緒啟動了子執行緒之後,在等待子執行緒執行完畢才繼續執行下面的操作。 注意:join()方法只有在start()方法之後才可以生效。可以參考下面的程式碼,將join()方法寫在start()方法之前。
	public static void main(String[] args) {
		JoinRunnable runnable1 = new JoinRunnable();
		Thread thread1 = new Thread(runnable1, "執行緒1");

		System.out.println("主執行緒開始執行!");
		try {
			thread1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		thread1.start();

		System.out.println("主執行緒執行結束!");
	}
執行結果: 主執行緒開始執行!
主執行緒執行結束!
執行緒1開始執行!
執行緒1執行了[1]次
執行緒1執行了[2]次
執行緒1執行了[3]次
執行緒1執行了[4]次
執行緒1執行了[5]次 雖然加入了join()方法,但是是線上程啟動的start()方法之前呼叫的,所以join()方法不生效。具體原因可以參考一下原始碼
    public final void join()
        throws InterruptedException
    {
        join(0L);
    }
    public final synchronized void join(long l)
        throws InterruptedException
    {
        long l1 = System.currentTimeMillis();
        long l2 = 0L;
        if(l < 0L)
            throw new IllegalArgumentException("timeout value is negative");
        if(l == 0L)
            for(; isAlive(); wait(0L));//isAlive()是判斷當前執行緒的狀態,wait(0)是指等待直到執行緒結束
        else
            do
            {
                if(!isAlive())
                    break;
                long l3 = l - l2;
                if(l3 <= 0L)
                    break;
                wait(l3);
                l2 = System.currentTimeMillis() - l1;
            } while(true);
    }
從原始碼可以看出,join()方法呼叫了join(long l)方法,引數l的值為0,之後在判斷中如果l的值為0,則執行一個迴圈,迴圈執行的條件為當前執行緒是否isAlive(),就是指執行緒是否已經執行,如果執行緒未啟動(即未呼叫start方法),則迴圈條件不成立,直接退出,也就是說join()方法不生效。 在看這裡的原始碼的時候發現了一個小問題,對於isAlive()和wait()方法的作用物件是有點讓人疑惑的,isAlive()判斷當前執行緒的狀態,還好理解,在上面的例子中就是指thread1。但是wait()方法的作用物件到底是誰,產生了疑問,按照jdk的說法等待的應該是當前執行緒,那就是thread1,但是實際執行中,等待的是主執行緒,有點讓人摸不著頭腦。之後在網上找到這篇文章,裡面說當我們在synchronized塊 中呼叫鎖物件的wait()方法,那麼呼叫執行緒就會阻塞在wait()語句那裡,直到其他執行緒呼叫執行緒的notify()方法。這裡的呼叫執行緒也就是指主執行緒,因為wai()方法是在Thread類中的synchronized方法join(long l)中呼叫的,所以主執行緒阻塞,直到執行緒執行完畢呼叫了notify()方法才繼續執行,這個說法先記錄一下,以備以後考證。

最後一點小插曲: 在編碼的時候嘗試過在start()方法和join()方法之間加入一些內容,也就是說呼叫start()方法啟動執行緒之後不立即呼叫join()方法,想看一下是否出現:先啟動子執行緒,執行相關操作,然後再執行主執行緒中的內容,再等待子執行緒執行結束的情況。示例程式碼:
	public static void main(String[] args) {
		JoinRunnable runnable1 = new JoinRunnable();
		Thread thread1 = new Thread(runnable1, "執行緒1");

		System.out.println("主執行緒開始執行!");
		thread1.start();
		//啟動子執行緒之後主執行緒休眠1秒	
<span style="white-space:pre">		</span>try {
			Thread.sleep(1000);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
		try {
			thread1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		System.out.println("主執行緒執行結束!");
	}
執行結果: 主執行緒開始執行!
執行緒1開始執行!
執行緒1執行了[1]次
執行緒1執行了[2]次
執行緒1執行了[3]次
執行緒1執行了[4]次
執行緒1執行了[5]次
主執行緒執行結束! 實際執行的時候主執行緒休眠1秒是在子執行緒執行結束之後才執行的,由此可以推斷,只要在start()方法之後呼叫了join()方法,不管這兩個方法之間加入多少內容,主執行緒都會優先等待子執行緒執行結束才會繼續往下執行。