Java線程:新特徵-條件變量

Java線程:新特徵-條件變量
 
條件變量是Java5線程中很重要的一個概念,顧名思義,條件變量就是表示條件的一種變量。可是必須說明,這裏的條件是沒有實際含義的,僅僅是個標記而已,而且條件的含義每每經過代碼來賦予其含義。
 
這裏的條件和普通意義上的條件表達式有着天壤之別。
 
條件變量都實現了java.util.concurrent.locks.Condition接口,條件變量的實例化是經過一個Lock對象上調用newCondition()方法來獲取的,這樣,條件就和一個鎖對象綁定起來了。所以,Java中的條件變量只能和鎖配合使用,來控制併發程序訪問競爭資源的安全。
 
條件變量的出現是爲了更精細控制線程等待與喚醒,在Java5以前,線程的等待與喚醒依靠的是Object對象的wait()和notify()/notifyAll()方法,這樣的處理不夠精細。
 
而在Java5中,一個鎖能夠有多個條件,每一個條件上能夠有多個線程等待,經過調用await()方法,可讓線程在該條件下等待。當調用signalAll()方法,又能夠喚醒該條件下的等待的線程。有關Condition接口的API能夠具體參考JavaAPI文檔。
 
條件變量比較抽象,緣由是他不是天然語言中的條件概念,而是程序控制的一種手段。
 
下面以一個銀行存取款的模擬程序爲例來揭蓋Java多線程條件變量的神祕面紗:
 
有一個帳戶,多個用戶(線程)在同時操做這個帳戶,有的存款有的取款,存款隨便存,取款有限制,不能透支,任何試圖透支的操做都將等待裏面有足夠存款才執行操做。
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* Java線程:條件變量
*
* @author leizhimin 2009-11-5 10:57:29
*/

public class Test {
         public static void main(String[] args) {
                 //建立併發訪問的帳戶
                MyCount myCount = new MyCount( "95599200901215522", 10000);
                 //建立一個線程池
                ExecutorService pool = Executors.newFixedThreadPool(2);
                Thread t1 = new SaveThread( "張三", myCount, 2000);
                Thread t2 = new SaveThread( "李四", myCount, 3600);
                Thread t3 = new DrawThread( "王五", myCount, 2700);
                Thread t4 = new SaveThread( "老張", myCount, 600);
                Thread t5 = new DrawThread( "老牛", myCount, 1300);
                Thread t6 = new DrawThread( "胖子", myCount, 800);
                 //執行各個線程
                pool.execute(t1);
                pool.execute(t2);
                pool.execute(t3);
                pool.execute(t4);
                pool.execute(t5);
                pool.execute(t6);
                 //關閉線程池
                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 {
                         if (cash - x < 0) {
                                _draw.await();             //阻塞取款操做
                        } else {
                                cash -= x;                     //取款
                                System.out.println(name + "取款" + x + ",當前餘額爲" + cash);
                        }
                        _save.signalAll();             //喚醒全部存款操做
                } catch (InterruptedException e) {
                        e.printStackTrace();
                } finally {
                        lock.unlock();                     //釋放鎖
                }
        }
}
 
 
李四存款3600,當前餘額爲13600
張三存款2000,當前餘額爲15600
老張存款600,當前餘額爲16200
老牛取款1300,當前餘額爲14900
胖子取款800,當前餘額爲14100
王五取款2700,當前餘額爲11400

Process finished with exit code 0
 
假如咱們不用鎖和條件變量,如何實現此功能呢?下面是實現代碼:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* Java線程:不用條件變量
*
* @author leizhimin 2009-11-5 10:57:29
*/

public class Test {
         public static void main(String[] args) {
                 //建立併發訪問的帳戶
                MyCount myCount = new MyCount( "95599200901215522", 10000);
                 //建立一個線程池
                ExecutorService pool = Executors.newFixedThreadPool(2);
                Thread t1 = new SaveThread( "張三", myCount, 2000);
                Thread t2 = new SaveThread( "李四", myCount, 3600);
                Thread t3 = new DrawThread( "王五", myCount, 2700);
                Thread t4 = new SaveThread( "老張", myCount, 600);
                Thread t5 = new DrawThread( "老牛", myCount, 1300);
                Thread t6 = new DrawThread( "胖子", myCount, 800);
                 //執行各個線程
                pool.execute(t1);
                pool.execute(t2);
                pool.execute(t3);
                pool.execute(t4);
                pool.execute(t5);
                pool.execute(t6);
                 //關閉線程池
                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;                             //帳戶餘額

        MyCount(String oid, int cash) {
                 this.oid = oid;
                 this.cash = cash;
        }

         /**
         * 存款
         *
         * @param x        操做金額
         * @param name 操做人
         */

         public synchronized void saving( int x, String name) {
                 if (x > 0) {
                        cash += x;                     //存款
                        System.out.println(name + "存款" + x + ",當前餘額爲" + cash);
                }
                notifyAll();             //喚醒全部等待線程。
        }

         /**
         * 取款
         *
         * @param x        操做金額
         * @param name 操做人
         */

         public synchronized void drawing( int x, String name) {
                 if (cash - x < 0) {
                         try {
                                wait();
                        } catch (InterruptedException e1) {
                                e1.printStackTrace();
                        }
                } else {
                        cash -= x;                     //取款
                        System.out.println(name + "取款" + x + ",當前餘額爲" + cash);
                }
                notifyAll();             //喚醒全部存款操做
        }
}
 
輸出結果爲:
李四存款3600,當前餘額爲13600
王五取款2700,當前餘額爲10900
老張存款600,當前餘額爲11500
老牛取款1300,當前餘額爲10200
胖子取款800,當前餘額爲9400
張三存款2000,當前餘額爲11400

Process finished with exit code 0
 
結合先前同步代碼知識,觸類旁通,將此例改成同步代碼塊來實現,代碼以下:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* Java線程:改成同步代碼塊
*
* @author leizhimin 2009-11-5 10:57:29
*/

public class Test {
         public static void main(String[] args) {
                 //建立併發訪問的帳戶
                MyCount myCount = new MyCount( "95599200901215522", 10000);
                 //建立一個線程池
                ExecutorService pool = Executors.newFixedThreadPool(2);
                Thread t1 = new SaveThread( "張三", myCount, 2000);
                Thread t2 = new SaveThread( "李四", myCount, 3600);
                Thread t3 = new DrawThread( "王五", myCount, 2700);
                Thread t4 = new SaveThread( "老張", myCount, 600);
                Thread t5 = new DrawThread( "老牛", myCount, 1300);
                Thread t6 = new DrawThread( "胖子", myCount, 800);
                 //執行各個線程
                pool.execute(t1);
                pool.execute(t2);
                pool.execute(t3);
                pool.execute(t4);
                pool.execute(t5);
                pool.execute(t6);
                 //關閉線程池
                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;                             //帳戶餘額

        MyCount(String oid, int cash) {
                 this.oid = oid;
                 this.cash = cash;
        }

         /**
         * 存款
         *
         * @param x        操做金額
         * @param name 操做人
         */

         public void saving( int x, String name) {
                 if (x > 0) {
                         synchronized ( this) {
                                cash += x;                     //存款
                                System.out.println(name + "存款" + x + ",當前餘額爲" + cash);
                                notifyAll();             //喚醒全部等待線程。
                        }
                }
        }

         /**
         * 取款
         *
         * @param x        操做金額
         * @param name 操做人
         */

         public synchronized void drawing( int x, String name) {
                 synchronized ( this) {
                         if (cash - x < 0) {
                                 try {
                                        wait();
                                } catch (InterruptedException e1) {
                                        e1.printStackTrace();
                                }
                        } else {
                                cash -= x;                     //取款
                                System.out.println(name + "取款" + x + ",當前餘額爲" + cash);
                        }
                }
                notifyAll();             //喚醒全部存款操做
        }
}
 
李四存款3600,當前餘額爲13600
王五取款2700,當前餘額爲10900
老張存款600,當前餘額爲11500
老牛取款1300,當前餘額爲10200
胖子取款800,當前餘額爲9400
張三存款2000,當前餘額爲11400

Process finished with exit code 0
 
對比以上三種方式,從控制角度上講,第一種最靈活,第二種代碼最簡單,第三種容易犯錯。
相關文章
相關標籤/搜索