java Condition條件變數的通俗易懂解釋
最近在看pthread方面的書,看到條件變數一節的時候,回憶了下java中條件變數的使用方式。
java中條件變數都實現了java.util.concurrent.locks.Condition介面,條件變數的例項化是通過一個Lock物件上呼叫newCondition()方法來獲取的,這樣,條件就和一個鎖物件繫結起來了。因此,Java中的條件變數只能和鎖配合使用,來控制併發程式訪問競爭資源的安全。
條件變數的出現是為了更精細控制執行緒等待與喚醒,在Java5之前,執行緒的等待與喚醒依靠的是Object物件的wait()和notify()/notifyAll()方法,這樣的處理不夠精細。
通熟易懂的說,就是消費者/生產者的場景中,在原來的基礎上,增加了佇列滿時及時通知消費者,佇列空時及時通知生產者的優化,通常是兩個條件變數一起出現,一個控制值,但兩個條件變數可以毫無關係,終歸來說還是在Lock的範圍內。所以,從本質上來說,是對Object監視器的場景性優化,而不是全新機制的引入。
從現實應用角度來說,它們常被用於下列場景:
1、寫log。比如每1秒或者commit或者日誌大於1/3m時候都寫入。快取中大於1/3m時需要等寫入完成才能commit。
而在Java5中,一個鎖可以有多個條件,每個條件上可以有多個執行緒等待,通過呼叫await()方法,可以讓執行緒在該條件下等待。當呼叫signalAll()方法,又可以喚醒該條件下的等待的執行緒。有關Condition介面詳細說明可以具體參考JavaAPI文件。
如下:public class TestConditon
public static void main(String[] args) { // 建立併發訪問的賬戶 MyCount myCount = new MyCount("95599200901215522", 10000); // 建立一個執行緒池 ExecutorService pool = Executors.newFixedThreadPool(3); #假設改成2會怎麼樣?? Thread t1 = new SaveThread("張三", myCount, 1000); Thread t2 = new SaveThread("李四", myCount, 1000); Thread t3 = new DrawThread("王五", myCount, 12600); Thread t4 = new SaveThread("老張", myCount, 600); Thread t5 = new DrawThread("老牛", myCount, 1300); Thread t6 = new DrawThread("胖子", myCount, 800); Thread t7 = new SaveThread("測試", myCount, 2100); // 執行各個執行緒 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); pool.execute(t6); pool.execute(t7); // 關閉執行緒池 pool.shutdown(); } } /** * 存款執行緒類 */ class SaveThread extends Thread { private String name; // 操作人 private MyCount myCount; // 賬戶 private int x; // 存款金額 SaveThread(String name, MyCount myCount, int x) { this.name = name; this.myCount = myCount; this.x = x; } public void run() { myCount.saving(x, name); } } /** * 取款執行緒類 */ class DrawThread extends Thread { private String name; // 操作人 private MyCount myCount; // 賬戶 private int x; // 存款金額 DrawThread(String name, MyCount myCount, int x) { this.name = name; this.myCount = myCount; this.x = x; } public void run() { myCount.drawing(x, name); } } /** * 普通銀行賬戶,不可透支 */ class MyCount { private String oid; // 賬號 private int cash; // 賬戶餘額 private Lock lock = new ReentrantLock(); // 賬戶鎖 private Condition _save = lock.newCondition(); // 存款條件 private Condition _draw = lock.newCondition(); // 取款條件 MyCount(String oid, int cash) { this.oid = oid; this.cash = cash; } /** * 存款 * * @param x * 操作金額 * @param name * 操作人 */ public void saving(int x, String name) { lock.lock(); // 獲取鎖 if (x > 0) { cash += x; // 存款 System.out.println(name + "存款" + x + ",當前餘額為" + cash); } _draw.signalAll(); // 喚醒所有等待執行緒。 lock.unlock(); // 釋放鎖 } /** * 取款 * * @param x * 操作金額 * @param name * 操作人 */ public void drawing(int x, String name) { lock.lock(); // 獲取鎖 try { while (cash - x < 0) { _draw.await(); // 阻塞取款操作, await之後就隱示自動釋放了lock,直到被喚醒自動獲取 System.out.println(name + "阻塞中"); } { cash -= x; // 取款 System.out.println(name + "取款" + x + ",當前餘額為" + cash); } _save.signalAll(); // 喚醒所有存款操作 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); // 釋放鎖 } } }
需要注意的是,在共用一個執行緒池的設計中,特別要注意餓死現象(就像上下高速如果公用車道的話,萬一進入的10車全部佔坑了,高速裡面又滿了的話,想出的都出不來,進的進不去,就出現餓死現象了),如果有大量的消費者使得生產者執行緒無法再執行的話,就會出現該問題,在上述例子中,將執行緒池數量從3改成2就可以多次測試中發現程式hang了。