2.使用synchronized關鍵字實現多線程的同步和互斥(不同線程同時讀寫同一數據)

利用能量守恆定律實現多線程的同步和互斥
- EnergySystem.java:能量類
- EnergySystemTest.java:測試Main類
- EnergyTransferTask.java:任務線程線程


synchronized

synchronized關鍵字,代表這個方法加鎖,相當於不管哪一個線程(例如線程A),運行到這個方法時,都要檢查有沒有其它線程B(或者C、D等)正在用這個方法(或者該類的其他同步方法),有的話要等正在使用synchronized方法的線程B(或者C、D)運行完這個方法後再運行此線程A,沒有的話,鎖定調用者,然後直接運行。它包括兩種用法:synchronized 方法和 synchronized塊。
Java語言的關鍵字,可用來給對象和方法或者代碼塊加鎖,當它鎖定一個方法或者一個代碼塊的時候,同一時刻最多隻有一個線程執行這段代碼。當兩個併發線程訪問同一個對象object中的這個加鎖同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊。然而,當一個線程訪問object的一個加鎖代碼塊時,另一個線程仍然可以訪問該object中的非加鎖代碼塊。

不加鎖(synchronized )的情況

/**
 * 宇宙的能量系統 遵循能量守恆定律: 能量不會憑空創生或消失,只會從一處轉移到另一處
 */
public class EnergySystem {

    // 能量盒子,能量存貯的地方
    private final double[] energyBoxes;
    private final Object lockObj = new Object();

    /**
     * 
     * @param n
     *            能量盒子的數量
     * @param initialEnergy
     *            每個能量盒子初始含有的能量值
     */
    public EnergySystem(int n, double initialEnergy) {
        energyBoxes = new double[n];
        for (int i = 0; i < energyBoxes.length; i++)
            energyBoxes[i] = initialEnergy;
    }

    /**
     * 能量的轉移,從一個盒子到另一個盒子
     * 
     * @param from
     *            能量源
     * @param to
     *            能量終點
     * @param amount
     *            能量值
     */
    public void transfer(int from, int to, double amount) {
        if (energyBoxes[from] > amount) {
            System.out.print(Thread.currentThread().getName());
            energyBoxes[from] -= amount;
            System.out.printf("從%d轉移%10.2f單位能量到%d", from, amount, to);
            energyBoxes[to] += amount;
            System.out.printf(" 能量總和:%10.2f%n", getTotalEnergies());
        } else {
            return;
        }
    }

    /**
     * 獲取能量世界的能量總和
     */
    public double getTotalEnergies() {
        double sum = 0;
        for (double amount : energyBoxes)
            sum += amount;
        return sum;
    }

    /**
     * 返回能量盒子的長度
     */
    public int getBoxAmount() {
        return energyBoxes.length;
    }

}
    public class EnergyTransferTask implements Runnable{

    //共享的能量世界
    private EnergySystem energySystem;
    //能量轉移的源能量盒子下標
    private int fromBox;
    //單次能量轉移最大單元
    private double maxAmount;
    //最大休眠時間(毫秒)
    private int DELAY = 10;


    public EnergyTransferTask(EnergySystem energySystem, int from, double max){
        this.energySystem = energySystem;
        this.fromBox = from;
        this.maxAmount = max;
    }

    public void run() {
        int c = 3;
        try{
            while (c > 0){
                c--;
                int toBox = (int) (energySystem.getBoxAmount()* Math.random());
                double amount = maxAmount * Math.random();
                energySystem.transfer(fromBox, toBox, amount);
                Thread.sleep((int) (DELAY * MansferTask(eng, i, INITIAL_ENERGY);
            Thread t = new Thread(task,"TransferThread_"+i);
            t.start();
        }
    }

}
public class EnergySystemTest {

    //將要構建的能量世界中能量盒子數量
    public static final int BOX_AMOUNT = 10;
    //每個盒子初始能量
    public static final double INITIAL_ENERGY = 100;

    public static void main(String[] args){
        EnergySystem eng = new EnergySystem(BOX_AMOUNT, INITIAL_ENERGY);
        for (int i = 0; i < BOX_AMOUNT; i++){
            EnergyTransferTask task = new EnergyTransferTask(eng, i, INITIAL_ENERGY);
            Thread t = new Thread(task,"TransferThread_"+i);
            t.start();
        }
    }

}

代碼功能

現在的代碼描述的是有10個能量盒子,然後啓動10個線程,每個線程會執行三次操作:將A盒子中能量轉移到B盒子中,但是我們發現,能量總和減少啦,爲什麼呢?
這裏寫圖片描述
舉個例子:線程1讀取盒子A的數據5000,將其加500,此時線程2讀取盒子A的數據5000(因爲還有寫入,所以盒子A還是5000),將其加900,再將其寫入,此時盒子A爲5900,但此時線程A也要進行寫操作,這時盒子A爲5500,所以……

輸出樣例

TransferThread_3TransferThread_7TransferThread_2從2轉移 53.56單位能量到3TransferThread_6從6轉移 40.02單位能量到5 能量總和: 937.16
TransferThread_1從1轉移 2.30單位能量到8 能量總和: 937.16
TransferThread_0從0轉移 27.61單位能量到9TransferThread_9TransferThread_4從4轉移 82.48單位能量到7 能量總和: 927.92
TransferThread_8從8轉移 68.90單位能量到3 能量總和: 927.92
TransferThread_5從5轉移 33.59單位能量到3 能量總和: 927.92
TransferThread_1從9轉移 9.24單位能量到3 能量總和: 866.17
能量總和: 937.16
能量總和: 937.16
從7轉移 43.20單位能量到3 能量總和: 909.37
從3轉移 19.64單位能量到7 能量總和: 929.01
TransferThread_0從0轉移 7.07單位能量到7 能量總和: 929.01
從1轉移 70.99單位能量到2 能量總和: 1000.00
TransferThread_7從7轉移 94.67單位能量到3 能量總和: 1000.00
TransferThread_5從5轉移 66.16單位能量到3 能量總和: 1000.00
TransferThread_7從7轉移 59.33單位能量到4 能量總和: 1000.00
TransferThread_4TransferThread_6從6轉移 12.24單位能量到8 能量總和: 997.95
從4轉移 2.05單位能量到4 能量總和: 1000.00
TransferThread_3從3轉移 61.92單位能量到4 能量總和: 1000.00
TransferThread_2從2轉移 65.65單位能量到5 能量總和: 1000.00
TransferThread_8TransferThread_9從9轉移 44.34單位能量到3 能量總和: 977.95
從8轉移 22.05單位能量到4 能量總和: 1000.00
TransferThread_1從1轉移 1.77單位能量到8 能量總和: 1000.00
TransferThread_4從4轉移 25.64單位能量到5 能量總和: 1000.00
TransferThread_3從3轉移 83.27單位能量到6 能量總和: 1000.00

加鎖操作,修改EnergySystem.java

/**
 * 宇宙的能量系統 遵循能量守恆定律: 能量不會憑空創生或消失,只會從一處轉移到另一處
 */
public class EnergySystem {

    // 能量盒子,能量存貯的地方
    private final double[] energyBoxes;
    private final Object lockObj = new Object();

    /**
     * 
     * @param n
     *            能量盒子的數量
     * @param initialEnergy
     *            每個能量盒子初始含有的能量值
     */
    public EnergySystem(int n, double initialEnergy) {
        energyBoxes = new double[n];
        for (int i = 0; i < energyBoxes.length; i++)
            energyBoxes[i] = initialEnergy;
    }

    /**
     * 能量的轉移,從一個盒子到另一個盒子
     * 
     * @param from
     *            能量源
     * @param to
     *            能量終點
     * @param amount
     *            能量值
     */
    public void transfer(int from, int to, double amount) {

        synchronized (lockObj) {
            // while循環,保證條件不滿足時任務都會被條件阻擋
            // 而不是繼續競爭CPU資源
            while (energyBoxes[from] < amount) {
                try {
                    // 條件不滿足, 將當前線程放入Wait Set
                    lockObj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.print(Thread.currentThread().getName());
            energyBoxes[from] -= amount;
            System.out.printf("從%d轉移%10.2f單位能量到%d", from, amount, to);
            energyBoxes[to]  amount;
            System.out.printf(" 能量總和:%10.2f%n", getTotalEnergies());
            // 喚醒所有在lockObj對象上等待的線程
            lockObj.notifyAll();
        }

    }

    /**
     * 獲取能量世界的能量總和
     */
    public double getTotalEnergies() {
        double sum = 0;
        for (double amount : energyBoxes)
            sum += amount;
        return sum;
    }

    /**
     * 返回能量盒子的長度
     */
    public int getBoxAmount() {
        return energyBoxes.length;
    }

}

鎖的應用

當程序執行到transfer方法時,通過synchronized (lockObj)會將當前資源(盒子A)鎖住,其他線程無法獲取次資源,當energyBoxes[from]小於amount時,即當前數據不符合操作要求,執行lockObj.wait()方法,將資源放開,此線程進入Wait Set中等待其他線程喚醒(notifyAll(),notify()),當數據符合操作且操作結束後,會調用notifyAll方法釋放線程,這是其他線程就可以獲取當前資源了,達到總能量始終是一定的。

notify()和notifyAll()都是Object對象用於通知處在等待該對象的線程的方法。兩者的最大區別在於:
notifyAll使所有原來在該對象上等待被notify的線程統統退出wait的狀態,變成等待該對象上的鎖,一旦該對象被解鎖,他們就會去競爭。
notify則文明得多他只是選擇一個wait狀態線程進行通知,並使它獲得該對象上的鎖,但不驚動其他同樣在等待被該對象notify的線程們,當第一個線程運行完畢以後釋放對象上的鎖此時如果該對象沒有再次使用notify語句,則即便該對象已經空閒,其他wait狀態等待的線程由於沒有得到該對象的通知,繼續處在wait狀態,直到這個對象發出一個notify或notifyAll,它們等待的是被notify或notifyAll,而不是鎖。

輸出樣例

TransferThread_6從6轉移 62.69單位能量到1 能量總和: 1000.00
TransferThread_1從1轉移 88.95單位能量到2 能量總和: 1000.00
TransferThread_3從3轉移 15.16單位能量到1 能量總和: 1000.00
TransferThread_4從4轉移 37.62單位能量到2 能量總和: 1000.00
TransferThread_5從5轉移 88.83單位能量到6 能量總和: 1000.00
TransferThread_0從0轉移 57.21單位能量到7 能量總和: 1000.00
TransferThread_2從2轉移 67.80單位能量到0 能量總和: 1000.00
TransferThread_7從7轉移 55.19單位能量到8 能量總和: 1000.00
TransferThread_8從8轉移 75.56單位能量到8 能量總和: 1000.00
TransferThread_9從9轉移 21.01單位能量到6 能量總和: 1000.00
TransferThread_3從3轉移 75.10單位能量到8 能量總和: 1000.00
TransferThread_6從6轉移 17.85單位能量到4 能量總和: 1000.00
TransferThread_7從7轉移 54.41單位能量到4 能量總和: 1000.00
TransferThread_0從0轉移 65.67單位能量到6 能量總和: 1000.00
TransferThread_9從9轉移 11.83單位能量到7 能量總和: 1000.00
TransferThread_2從2轉移 59.20單位能量到8 能量總和: 1000.00
TransferThread_9從9轉移 32.79單位能量到3 能量總和: 1000.00
TransferThread_3從3轉移 11.00單位能量到9 能量總和: 1000.00
TransferThread_4從4轉移 41.82單位能量到4 能量總和: 1000.00
TransferThread_8從8轉移 53.70單位能量到3 能量總和: 1000.00
TransferThread_6從6轉移 81.85單位能量到8 能量總和: 1000.00
TransferThread_2從2轉移 15.96單位能量到0 能量總和: 1000.00
TransferThread_4從4轉移 32.41單位能量到9 能量總和: 1000.00
TransferThread_0從0轉移 59.02單位能量到0 能量總和: 1000.00
TransferThread_8從8轉移 67.92單位能量到2 能量總和: 1000.00

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章