Mutex

如果可以不要用synchronized來做到thread-safe,那麼可以考慮使用mutex。

現在來修改在Single Threaded Execution Pattern所用到的Gate類別。

public class Gate {
    private int counter = 0;
    private String name = "Nobody";
    private String address = "Nowhere";
    private final Mutex mutex = new Mutex();
    public void pass(String name, String address) { // 並非synchronized
        mutex.lock();
        try {
            this.counter++;
            this.name = name;
            this.address = address;
            check();
        } finally {
            mutex.unlock();
        }
    }
    public String toString() { // 並非synchronized
        String s = null;
        mutex.lock();
        try {
            s = "No." + counter + ": " + name + ", " + address;
        } finally {
            mutex.unlock();
        }
        return s;
    }
    private void check() {
        if (name.charAt(0) != address.charAt(0)) {
            System.out.println("***** BROKEN ***** " + toString());
        }
    }
}

問題是,Mutex這個類別要如何設計? 我們設計一個lock方法,並且在其中設定一個busy欄位,表示目前是不是已經釋放鎖定了。同時也設定一個unlock方法,釋放鎖定。

ok,現在來設計一個simple mutex,建立Mutex.java

public final class Mutex {
    private boolean busy = false;
    //必須設定成syncronized,因為busy是一個shared resource
    public synchronized void lock() {
        while (busy) {
            try {
                //當還沒拿到鎖定的時候,先進入wait set
                wait();
            } catch (InterruptedException e) {
            }
        }
        busy = true;
    }
    public synchronized void unlock() {
        busy = false;
        //叫醒其它在wait set中的執行緒。
        notifyAll();
    }
}

但是這樣設計並不好,雖然在Gate類別運作良好,但假設有某個執行緒連續呼叫兩次lock,則會變成在還沒釋放鎖定之前就把自己關到wait set中。 另外,即使是沒有獲得鎖定的執行緒,也可以呼叫unlock方法,邏輯上會變得很奇怪。

以下是一個改良過後的Mutex.java。利用locks紀錄目前的lock數量,並且紀錄一個owner來紀錄誰呼叫了這個lock。

public final class Mutex {
    private long locks = 0;
    private Thread owner = null;
    public synchronized void lock() {
        Thread me = Thread.currentThread();
        while (locks > 0 && owner != me) {
            try {
                //若被叫醒回來發現lock大於0的話,則再回去wait set。
                wait();
            } catch (InterruptedException e) {
            }
        }
        // locks == 0 || owner == me
        owner = me;
        locks++;
    }
    public synchronized void unlock() {
        Thread me = Thread.currentThread();
        if (locks == 0 || owner != me) {
            return;
        }
        // locks > 0 && owner == me
        locks--;
        if (locks == 0) {
            owner = null;
            notifyAll();
        }
    }
}