java學習歷程:wait()與join()的理解誤區
之所以會寫這篇部落格,完全是因為博主一直以來對這兩個函式的理解有所模糊,於是在網上查閱資料,捋順了一些東西,在拿出來分享的同時也希望大家能對我有所指正。
一、首先上程式碼,這段程式碼是關於wait()的使用。
package test; class Person{ private String Name; private int Age; private boolean isEmpty = true; //表示共享資源物件是否為空,如果為 true,表示需要生產,如果為 false,則有資料了,不要生產 public synchronized void push(String aName, int aAge){ try { while(isEmpty == false){ this.wait();//不寫引數就是無限等 } this.Name = aName; this.Age = aAge; Thread.sleep(10); isEmpty = false; this.notifyAll();//生產完畢,喚醒所有消費者 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public synchronized void pop(){ try { while(isEmpty == true){ this.wait(); } Thread.sleep(10); System.out.println(this.Name + " " + this.Age); isEmpty = true;//設定 isEmpty為true,表示需要生產者生產物件 this.notifyAll(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class Producer implements Runnable{ Person p = null; public Producer(Person p){ this.p = p; } @Override public void run() { // TODO Auto-generated method stub for(int i = 0;i<20;i++){ if(i%2==0){ p.push("Tom", 11); }else{ p.push("Marry", 22); } } } } class Consumer implements Runnable{ Person p = null; public Consumer(Person p){ this.p = p; } @Override public void run(){ for(int i = 0; i<20; i++){ this.p.pop(); } } } public class ThreadTest2 {//寫一個生產者消費者的演示程式 public static void main(String[] args){ Person p = new Person(); Thread t1 = new Thread(new Producer(p)); Thread t2 = new Thread(new Consumer(p)); t1.start(); t2.start();//總感覺執行緒的啟動有順序,而執行緒的執行沒有 } }
這是一段簡單的生產者與消費者的程式碼演示,生產一個person後消費一個person,出現Tom-11與Marry-22的交替顯示,以證明確實是生產者與消費者的程式碼段,程式碼出處http://www.cnblogs.com/ysocean/p/6896219.html,文中發現在synchronized程式碼塊中通過使用了wait()使得獲得該物件的物件鎖的執行緒進入物件的等待佇列,直至喚醒(notifyAll())。
二、上程式碼,這段程式碼是關於join()的使用。
package test; public class ThreadTest3 { class ThreadImp implements Runnable { public synchronized void run(){ try{ System.out.println(Thread.currentThread().getName() + "Begin"); Thread.sleep(10); /*System.out.println(Thread.currentThread().getName() + "process"); Thread.sleep(10);*/ System.out.println(Thread.currentThread().getName() + "end"); Thread.sleep(10); }catch(InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args)throws Exception{ ThreadTest3 T3 = new ThreadTest3(); ThreadImp th = T3.new ThreadImp(); //子執行緒 Thread t = new Thread(th,"執行緒1"); Thread t2 = new Thread(th,"執行緒2"); Thread t3 = new Thread(th,"執行緒3"); Thread t4 = new Thread(th,"執行緒4"); Thread t5 = new Thread(th,"執行緒5"); t.start(); t.join(); t2.start(); t2.join(); t3.start(); t3.join(); t4.start(); t4.join(); t5.start(); t5.join(); System.out.println("the final");//main執行緒最後執行 //t2.join(); /*try{ t.join(1000);//join的用法 if (t.isAlive()) { System.out.println("執行緒1alive"); } else { System.out.println("執行緒1dead"); } System.out.println(Thread.currentThread().getName() + "finished"); } catch (InterruptedException e) { e.printStackTrace(); }*/ } }
關於join()方法,其實可以看作wait(),JDK原始碼中的實現也是基於呼叫wait()方法的,有興趣的可以自行研究一下,PS:Thread.join() 等同於Thread.wait(),無引數代表0,即無限等。上述程式碼通過呼叫join()實現了執行緒的有序執行。
三、關於這兩個方法的個人理解
舉個例子,現有兩個執行緒A與B,A中呼叫了B.join(),那麼A執行緒會停止,等B先執行完後A才開始執行。 那麼有人會想當然的理解成,B.join()等同於B.wait(),不就是B在wait,也就是B等待嗎,為什麼反而會讓A停運,讓B先執行呢?這也是博主之前一直很模糊的地方。要理解這裡,先假設一個物件D,需要知道其實wait()方法是D.wait(),也就是說獲取了物件D的物件鎖的執行緒釋放該物件鎖,重新進入物件的鎖獲取佇列中,與其他執行緒一起競爭該鎖,這也是為什麼第一段程式碼中,物件Person進行wait後,需要notifyAll的原因,而第二段程式碼,需要把執行緒1、2、3、4、5看作是一個個物件,PS:java中很多東西都可以看作物件,執行緒當然不例外。主執行緒main看作是呼叫這些物件的執行緒,join()是一個synchronized方法,即線上程物件1、2、3、4、5被main執行緒呼叫了join後,main執行緒會釋放該物件的物件鎖,進入該物件的鎖獲取佇列中,等待喚醒(例如該物件執行結束),這樣main執行緒才能繼續執行下去,這也是為什麼join能控制執行緒執行順序的原因所在。