Java併發編程-同步(六)

原文:http://blog.sina.com.cn/s/blog_4b6047bc010009dc.html

    線程除要對共享數據保證互斥性訪問外,往往還需保證線程的操作按照特定順序進行。解決多線程按照特定順序訪問共享數據的技術稱作同步。同步技術最常見的編程範式是同步保護塊。這種編程範式在操作前先檢測某種條件是否成立,如成立則繼續操作;如不成立則有兩種選擇,一種是簡單的循環檢測,直至此條件條件成立:
public void guardedOperation(){
  while(!condition_expression){
    System.out.println("Not ready yet, I have to wait again!");
  }
}

    這種方法非常消耗CPU資源,任何情況下都不應該使用這種方法。另種更好的方式是條件不成立時調用Object.wait方法掛起當前線程,使它一直等待,直至另一個線程發出激活事件。
當然該事件不一定是當前線程希望等待的事件。
public synchronized guardedOperation() {
    while(!condition_expression) {
        try {
            wait();
        } catch (InterruptedException e) {}
    }
    System.out.println("Now, condition met and it is ready!");
}
    這兒有兩點需要特別注意:
1.要在循環檢測中等待條件滿足,這是因爲中斷事件並不一定是當前線程所期望的事件。線程等待被中斷後應該繼續檢測條件,以便決定是否進入下一輪等待。
2.當前線程在對wait方法調用時,必須是已經獲得wait方法所屬對象的內部鎖。也就是說,wait方法必須在互斥塊或者互斥方法體內調用,否則就會發生NotOwnerException錯誤
。這種限制和前面所說的同步前提是互斥的說法是一致的。
    上面代碼更通用的寫法是:
...
synchronized(lock){
   while(!condition_expression){
      try{
         lock.wait();
      }catch(InterruptedException ie){}
   }  
   System.out.println("Now, condition met and it is ready!");  
}
...
    線程在synchronized語句獲取對象的內部鎖之後,在synchronized代碼塊期間就擁有了內部鎖。當判斷條件不成立時,可以調用該對象的wait方法進入等待狀態。
    注意持有鎖的線程在調用wait方法進入等待狀態之後,會自動釋放持有的鎖。這樣做的目的是允許其他的線程進入臨界區繼續操作,以防止死鎖的發生。

    舉生產者和消費者的例子。如果消費者在檢查時發現沒有產品生成,則調用wait方法等待生產者生產。如果此時消費者不釋放該鎖,生產者就會因爲獲取不到該鎖而處於阻塞狀態。而此時消費者卻在等待生產者生產出產品來,這樣雙方就進入死鎖狀態。因此wait方法需要在掛起線程後釋放該線程所擁有的鎖。
    當wait方法調用後,線程進入等待狀態,直至未來某刻其他線程獲得該鎖並調用其invokeAll(或invoke)方法將其喚醒。該線程通過如下類似的代碼激活等待在此鎖上
的線程:
public synchronized notifyOperation(){
   condition_expression=true;
   notifyAll();
}

    假設線程C因檢測到某種條件不滿足而進入等待狀態,激活C線程的P線程往往需要和C線程建立“發生過”關係。也就是說程序期望線程P和C之間按照先P後C的順序執行。
對於生產者和消費者例子來說,P就是生產者,C就是消費者,它們之間存在從P到C的“發生過”關係。
    線程P在調用notify或者notifyAll方法時需要首先獲得該對象的鎖,因此這些代碼也需要放在synchronized代碼體內。上面的激活方法更通用的寫法是:
  ...
  synchronized(lock){
     condition_expression=true;
     lock.notifyAll();
  }
  ...

    現舉生產者和消費者之間同步的例子。爲了簡化,假設生產者和消費者之間只共享一個容器。生產者生產出對象後放在在該容器中,而消費者從該
容器中獲取該對象進行消費。消費者和生者之間往往需要建立雙向的“發生過”關係,即消費者只有在有東西才能消費,而生產者只有在有存放空間時才能生產。這兒爲了簡化,只假定保證消費者有東西可消費,生產者不管是否有空間可存放,只是將對象生產出來放在容器中。下面是這個例子的代碼:
public class TankContainer{
   private Tank tank;
   public synchronized void putTank(Tank tank){
      //Dont bother to check whether it has room.
      this.tank=tank;
      notifyAll();
   }
   public synchronized Tank getTank(){
      //Check whether there's tank to consume
      while(tank==null){
         //No tank yet, let's wait.
         try{
             wait();
         }catch(InterruptedException e){}
      }
      Tank retValue=tank.
      tank=null; //Clear tank.
      return retValue;
   }
}
public ProducerThread extends Thread{
  //Shared TankContainer
  private TankContainer container;
  public ProducerThread(TankContainer container){
    this.container=container;
  }
  ...
  public void run(){
    while(true){
       Tank tank=produceTank();
       container.putTank(tank);   
    }
  }
  ...
}
public ConsumerThread extends Thread{
  //Shared TankContainer
  private TankContainer container;
  public ConsumerThread(TankContainer container){ 
    this.container=container;
  }
  ...
  public void run(){
    while(true){
      Tank tank=container.getTank();
      consumeTank(tank);     
    }
  }
  ...
}

public class ProducerConsumer{
  public static void main(String[]args){
    TankContainer container=new TankContainer();//Shared TankContainer
    new ProducerThread(container).start(); //Start to produce goods in its own thread.
    new ConsumerThread(container).start(); //Start to consume goods in its own thread.
  }
}

     總結一下,同步編程時應該要記住下面幾條:
1.兩個線程應該獲取同一個對象的鎖。這是獲取同步的互斥性前提。
2.消費者線程應在循環體內檢測條件是否成立。
3.消費者線程在條件沒有滿足時應調用鎖對象的wait方法等待。
4.wait方法被中斷後應進入下一輪條件檢測循環。
5.生產者線程應該在其操作或結束返回之前調用鎖對象的notify或notifyAll方法激活等待線程。
   補充一下notify和notifyAll方法的區別。notify激活等待隊列上的下一個線程。而notifyAll則激活所有等待線程。在生產者釋放鎖之後,這些被激活線程競爭獲取該
鎖。獲得該鎖的線程只有一個,它從wait中返回,進入下一輪條件檢測。沒有獲得鎖的線程繼續進入等待狀態,等待下一次激活事件。


    Java中除了通過互斥和同步技術來獲得代碼線程安全共性以外,還通過所謂恆量對象(immutable objects)的模式獲取線程安全性。其基本原理是恆量對象在創建完畢後就只能讀取,就
像final對象一樣。後面的文章將對immuable對象技術進行詳細描述。

發佈了1 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章