Java執行緒中yield與join方法的區別

ImportNew - Calarence發表於2015-02-11

長期以來,多執行緒問題頗為受到面試官的青睞。雖然我個人認為我們當中很少有人能真正獲得機會開發複雜的多執行緒應用(在過去的七年中,我得到了一個機會),但是理解多執行緒對增加你的信心很有用。之前,我討論了一個wait()和sleep()方法區別的問題,這一次,我將會討論join()和yield()方法的區別。坦白的說,實際上我並沒有用過其中任何一個方法,所以,如果你感覺有不恰當的地方,請提出討論。

Java執行緒排程的一點背景

在各種各樣的執行緒中,Java虛擬機器必須實現一個有優先權的、基於優先順序的排程程式。這意味著Java程式中的每一個執行緒被分配到一定的優先權,使用定義好的範圍內的一個正整數表示。優先順序可以被開發者改變。即使執行緒已經執行了一定時間,Java虛擬機器也不會改變其優先順序

優先順序的值很重要,因為Java虛擬機器和下層的作業系統之間的約定是作業系統必須選擇有最高優先權的Java執行緒執行。所以我們說Java實現了一個基於優先權的排程程式。該排程程式使用一種有優先權的方式實現,這意味著當一個有更高優先權的執行緒到來時,無論低優先順序的執行緒是否在執行,都會中斷(搶佔)它。這個約定對於作業系統來說並不總是這樣,這意味著作業系統有時可能會選擇執行一個更低優先順序的執行緒。(我憎恨多執行緒的這一點,因為這不能保證任何事情)

注意Java並不限定執行緒是以時間片執行,但是大多數作業系統卻有這樣的要求。在術語中經常引起混淆:搶佔經常與時間片混淆。事實上,搶佔意味著只有擁有高優先順序的執行緒可以優先於低優先順序的執行緒執行,但是當執行緒擁有相同優先順序的時候,他們不能相互搶佔。它們通常受時間片管制,但這並不是Java的要求。

理解執行緒的優先權

接下來,理解執行緒優先順序是多執行緒學習很重要的一步,尤其是瞭解yield()函式的工作過程。

  1. 記住當執行緒的優先順序沒有指定時,所有執行緒都攜帶普通優先順序。
  2. 優先順序可以用從1到10的範圍指定。10表示最高優先順序,1表示最低優先順序,5是普通優先順序。
  3. 記住優先順序最高的執行緒在執行時被給予優先。但是不能保證執行緒在啟動時就進入執行狀態。
  4. 與線上程池中等待執行機會的執行緒相比,當前正在執行的執行緒可能總是擁有更高的優先順序。
  5. 由排程程式決定哪一個執行緒被執行。
  6. t.setPriority()用來設定執行緒的優先順序。
  7. 記住線上程開始方法被呼叫之前,執行緒的優先順序應該被設定。
  8. 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY來設定優先順序

現在,當我們對執行緒排程和執行緒優先順序有一定理解後,讓我們進入主題。

yield()方法

理論上,yield意味著放手,放棄,投降。一個呼叫yield()方法的執行緒告訴虛擬機器它樂意讓其他執行緒佔用自己的位置。這表明該執行緒沒有在做一些緊急的事情。注意,這僅是一個暗示,並不能保證不會產生任何影響。

在Thread.java中yield()定義如下:

/**
  * A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore
  * this hint. Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilize a CPU.
  * Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect.
  */

public static native void yield();

讓我們列舉一下關於以上定義重要的幾點:

  • Yield是一個靜態的原生(native)方法
  • Yield告訴當前正在執行的執行緒把執行機會交給執行緒池中擁有相同優先順序的執行緒。
  • Yield不能保證使得當前正在執行的執行緒迅速轉換到可執行的狀態
  • 它僅能使一個執行緒從執行狀態轉到可執行狀態,而不是等待或阻塞狀態

yield()方法使用示例

在下面的示例程式中,我隨意的建立了名為生產者和消費者的兩個執行緒。生產者設定為最小優先順序,消費者設定為最高優先順序。在Thread.yield()註釋和非註釋的情況下我將分別執行該程式。沒有呼叫yield()方法時,雖然輸出有時改變,但是通常消費者行先列印出來,然後事生產者。

呼叫yield()方法時,兩個執行緒依次列印,然後將執行機會交給對方,一直這樣進行下去。

package test.core.threads;

public class YieldExample
{
   public static void main(String[] args)
   {
      Thread producer = new Producer();
      Thread consumer = new Consumer();

      producer.setPriority(Thread.MIN_PRIORITY); //Min Priority
      consumer.setPriority(Thread.MAX_PRIORITY); //Max Priority

      producer.start();
      consumer.start();
   }
}

class Producer extends Thread
{
   public void run()
   {
      for (int i = 0; i < 5; i++)
      {
         System.out.println("I am Producer : Produced Item " + i);
         Thread.yield();
      }
   }
}

class Consumer extends Thread
{
   public void run()
   {
      for (int i = 0; i < 5; i++)
      {
         System.out.println("I am Consumer : Consumed Item " + i);
         Thread.yield();
      }
   }
}

上述程式在沒有呼叫yield()方法情況下的輸出:

I am Consumer : Consumed Item 0
 I am Consumer : Consumed Item 1
 I am Consumer : Consumed Item 2
 I am Consumer : Consumed Item 3
 I am Consumer : Consumed Item 4
 I am Producer : Produced Item 0
 I am Producer : Produced Item 1
 I am Producer : Produced Item 2
 I am Producer : Produced Item 3
 I am Producer : Produced Item 4

上述程式在呼叫yield()方法情況下的輸出:

I am Producer : Produced Item 0
 I am Consumer : Consumed Item 0
 I am Producer : Produced Item 1
 I am Consumer : Consumed Item 1
 I am Producer : Produced Item 2
 I am Consumer : Consumed Item 2
 I am Producer : Produced Item 3
 I am Consumer : Consumed Item 3
 I am Producer : Produced Item 4
 I am Consumer : Consumed Item 4

join()方法

執行緒例項的方法join()方法可以使得在另一個執行緒的執行結束後再開始執行這個執行緒。如果join()方法被在一個執行緒例項上呼叫,當前執行著的執行緒將阻塞直到執行緒例項完成了執行。

//Waits for this thread to die.

public final void join() throws InterruptedException

在join()方法內設定超時,使得join()方法的影響在特定超時後無效。當超時時,主方法和任務執行緒申請執行的時候是平等的。然而,當涉及sleep時,join()方法依靠作業系統計時,所以你不應該假定join()方法將會等待你指定的時間。

像sleep,join通過丟擲InterruptedException對中斷做出回應。

join()方法使用示例

package test.core.threads;

public class JoinExample
{
   public static void main(String[] args) throws InterruptedException
   {
      Thread t = new Thread(new Runnable()
         {
            public void run()
            {
               System.out.println("First task started");
               System.out.println("Sleeping for 2 seconds");
               try
               {
                  Thread.sleep(2000);
               } catch (InterruptedException e)
               {
                  e.printStackTrace();
               }
               System.out.println("First task completed");
            }
         });
      Thread t1 = new Thread(new Runnable()
         {
            public void run()
            {
               System.out.println("Second task completed");
            }
         });
      t.start(); // Line 15
      t.join(); // Line 16
      t1.start();
   }
}

Output:

First task started
Sleeping for 2 seconds
First task completed
Second task completed

這是一些很小卻很重要的概念。在評論部分讓我知道你的想法。

相關文章