1. 程式人生 > >Java併發程式設計(詳解wait(), notify(),sleep())

Java併發程式設計(詳解wait(), notify(),sleep())

        上一篇部落格,重點講解了java中鎖的機制,省的在多執行緒之間出現混亂的局面,其實主要能夠理解鑰匙即可。如果要保證方法之間能夠獨立完全的執行,因此就必須所有的方法都共用一把鑰匙。然後小編最後也總結了一下,在此也再說一下。

        1.呼叫同一個物件中非靜態同步方法的執行緒將彼此阻塞。如果是不同物件,則每個執行緒有自己的物件的鎖,執行緒間彼此互不干預。

        2.呼叫同一個類中的靜態同步方法的執行緒將彼此阻塞,他們都所鎖在同一個class物件上

       這篇部落格來講一下,java中執行緒之間的同步技術,用到的也就是Object物件的幾個方法,正如標題所示wait、notify,sleep方法,為了學習執行緒之間的同步技術,小編查閱了很多部落格,在此也分享一下自己的學習經驗。

                  這裡詳細的說一下各個方法的說明

        1.notify()

        具體是怎麼個意思呢?就是用來喚醒在此物件上等待的單個執行緒。說的有點太專業。打個比方,現在有十棟大房子,裡面有很多被上了鎖的房間,奇怪的是鎖都是一樣的,更不可思議的是,現在只有一把鑰匙。而此時,張三用完鑰匙後,就會發出歸還鑰匙的提醒,就相當於發出notify()通知,但是要注意的是,此時鑰匙還在張三手中,只不過,當張三發出notify()通知後,JVM從那些整個沉睡的執行緒,喚醒一個。對應本例子,就是從其餘的九棟大房子中喚醒一家,至於提醒誰來拿這把鑰匙,就看JVM如何分配資源了。等到張三把鑰匙歸還後,那個被提醒的哪家,就可以使用該把鑰匙來開房間門了。與此相對應的,還有一個notifyAll()方法。這是什麼意思呢,還是本例,張三嗓門大,這時吼了一嗓子,即notifyAll(),所有沉睡的執行緒全都被吵醒了,當張三歸還鑰匙後,他們就可以競爭了,注意,剛才是JVM自動分配,而此時是執行緒之間競爭,比如優先順序等等條件,是有區別的。


 需要注意的是notify()方法執行後,並不是立即釋放鎖,而是等到加鎖的程式碼塊執行完後,才開始釋放的,相當於本例中,張三隻是發出了歸還的通知,但是鑰匙還沒有歸還,需要等到程式碼塊,執行完後,才可以歸還。

        2.wait()

        這個方法又是怎麼個意思呢?當執行到這個方法時,就把鑰匙歸還,開始睡覺了。Thread.sleep()與Object.wait()二者都可以暫停當前執行緒,釋放CPU控制權,主要的區別在於Object.wait()在釋放CPU同時,釋放了物件鎖的控制。

        下面來看一個例子,加入我們要實現三個執行緒之間的同步操作,如何來實現呢?加入有三個執行緒分別用來輸出A/B/C,如何能夠三個執行緒之間順序執行呢?這時候就需要採取執行緒之間同步的操作了,詳見下面的程式碼。

<span style="font-family:Comic Sans MS;font-size:18px;">package com.test;


public class MyThreadPrinter2 implements Runnable {

	private String name;
	private Object prev;
	private Object self;

	private MyThreadPrinter2(String name, Object prev, Object self) {
		this.name = name; // A B C
		this.prev = prev; // c a b
		this.self = self; // a b c
	}

	@Override
	public void run() {
		int count = 10;
		while (count > 0) {
			// 加鎖,鎖的鑰匙是prev
			synchronized (prev) {
				// 一把鎖,鎖的鑰匙是self變數
				synchronized (self) {
					System.out.print(name);
					count--;
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					// 喚醒另一個執行緒,但是也需要把本執行緒執行完後,才可以釋放鎖
					self.notify(); //a b c
				}

				try {
					// 釋放物件鎖,本執行緒進入休眠狀態,等待被喚醒
					prev.wait();  //睡覺覺了,等待被叫醒吧   // c a b
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

		}
	}

	public static void main(String[] args) throws Exception {
		Object a = new Object();
		Object b = new Object();
		Object c = new Object();
		MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
		MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
		MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);

		new Thread(pa).start();
		// 這樣才可以保證按照順序執行
		 Thread.sleep(10);
		new Thread(pb).start();
		 Thread.sleep(10);
		new Thread(pc).start();
		 Thread.sleep(10);
	}
}</span>

        下面來分析一下,首先通過執行緒之間的同步操作,上面的例子就會按照執行緒的順序來分別執行,最終輸出的結果就是ABCABCABC..............。

        在此還是打一個比方,程式剛開始的時候,建立了三個執行緒的物件,也就是代表有三座大房子,下面開始執行了,第一個大房子裡面有一個叫做張三的人員,打開了一個房子的鑰匙c,然後拿著鑰匙a,又一次打開了這個房子裡面的一個箱子,最後完成後,把箱子的鑰匙a給歸還了,執行完後,張三開始在這個c鑰匙的房子裡面漫長的沉睡,也就是prev.wati()方法;當第二個人李四來到房子後,同樣執行一邊操作,此時需要注意的是,李四扔出的是箱子鑰匙b,並在a鑰匙的房間隨著了;好吧懶貨,王五來到了第三個大房子,同樣執行了一遍操作,注意的是,王五扔出的是箱子鑰匙c,在鑰匙b房子睡著了。

        重點來了,王五扔出箱子鑰匙c後,此時就把在房子鑰匙c中的張三給喚醒了,等到王五把鑰匙歸還後,此時張三又開始周而復始的運作了;於是大家可以按照邏輯接著向下分析一下。

        在整個例子中需要注意一下幾點

        1.為了保證幾個執行緒先能夠順序執行,於是加入了Thread.sleep(10)

        2.每個物件執行wait()或者notify()方法時,只能在同一把鎖的房子裡面,例如

synchronized (self) {

System.out.print(name);

count--;

try {

Thread.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

// 喚醒另一個執行緒,但是也需要把本執行緒執行完後,才可以釋放鎖

self.notify(); //a b c

}

        房子的鎖是self,所以執行self.notify()代表把我房子的鑰匙給歸還了。

        有了上面的分析過程,下面我們出一道題,傳統執行緒同步通訊技術,子執行緒迴圈10次,接著主執行緒迴圈100次,又回到子執行緒迴圈10次,接著再回到主執行緒又迴圈100次,如此迴圈50次。這個例子又如何來實現呢?小編只在這裡貼出原始碼,網上有很多解釋的,可以按照小編的邏輯來分析一下。

<span style="font-family:Comic Sans MS;font-size:18px;">package com.test;


public class TraditionalThreadCommunication {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		final Business business = new Business();
		new Thread(
				new Runnable() {
					
					@Override
					public void run() {
					
						for(int i=1;i<=50;i++){
							business.sub(i);
						}
						
					}
				}
		).start();
		
		for(int i=1;i<=50;i++){
			business.main(i);
		}
		
	}

}
  class Business {
	  private boolean bShouldSub = true;
	  public synchronized void sub(int i){
		  while(!bShouldSub){
			  try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		  }
			for(int j=1;j<=10;j++){
				System.out.println("sub thread sequence of " + j + ",loop of " + i);
			}
		  bShouldSub = false;
		  this.notify();
	  }
	  
	  public synchronized void main(int i){
		  	while(bShouldSub){
		  		try {
					this.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
		  	}
			for(int j=1;j<=100;j++){
				System.out.println("main thread sequence of " + j + ",loop of " + i);
			}
			bShouldSub = true;
			this.notify();
	  }
  }
</span>