2/14/2014
1:31:00 PM 0

Mutex

Mutex 是 mutual exclusion 的縮寫, 中文譯為 "互斥",在多執行緒環境下,防止兩條以上的執行緒對一共用資源做同時存取的動作,一般做法是透過程式代碼 lock 的方式, 建立 critical section, 達到 mutex 的目的

下圖簡單表示 mutual exclusion 的意義
在 Java 1.4 可使用 synchronized 關鍵字來達到 mutex 的目的,但使用 synchronized 有些功能不易做到,例如
  1. 如果發現 lock 已被佔用,就只能等待,無法去處理其他的工作
  2. 在等待獲取鎖的狀況下,無法被 interrupt
  3. 自訂取得鎖的規則(公平鎖,非公平鎖)
Java 1.5 後新增 java.util.concurrent.locks package 補強 synchronized 的不足之處
  • ReentrantLock中文翻譯為"重入鎖",就字面上的意義代表當某個 thread 獲取某個鎖後,在未釋放鎖的情況下,可以獲得同一把鎖多次,因為 ReentrantLock 內部存在著一個計數器,計算取得鎖的次數,如果取得 lock 兩次,就必須 unlock 兩次才可真正的釋放鎖,這樣的設計可方便使用遞迴的方式呼叫(recursive call) lock()

    基本使用方式
    final Lock lock = new ReentrantLock();
    lock.lock();
    try { 
      // do something ...
    }
    finally {
      lock.unlock(); 
    }
    
    遞迴呼叫
    private final ReentrantLock lock = new ReentrantLock();
    
    public void execute() {
        try {
            lock.lock();//This lock supports a maximum of 2147483647 recursive locks by the same thread.
    
            System.out.println("lock|HoldCount=" + lock.getHoldCount());
    
            if (lock.getHoldCount() < 10)
                execute();//recursive locking by a single thread
    
        } finally {
            lock.unlock();
            System.out.println("unlock|HoldCount=" + lock.getHoldCount()
                + "|isHeldByCurrentThread=" + lock.isHeldByCurrentThread());
        }
    }
    
    從 Java 的 API Specification 我們可以看到 ReentrantLock 的 constructor 在不傳值的狀況下的狀況下,預設是個非公平鎖,如果我們要讓獲得鎖公平,就必需維持 thread 的排隊順序,這需要額外開銷,對執行效率是不利的,雖然獲取鎖的原則是不公平的,但 JVM 保證所有 thread 最終會獲得它們等待的鎖,避免 thread starvation

    ReentrantLock 有 lock, tryLock, lockInterruptibly 幾個獲取 lock 的 method
    • lock() : 如果拿不到 lock 就一直等待,直到拿到 lock 為止
    • tryLock() : 如果拿到鎖就 return true, 否則就 return false
    • tryLock(long timeout, TimeUnit unit) : 如果拿不到 lock, 則等待一段時間
    • lockInterruptibly() : 如果拿不到 lock 就一直等待, 在等待的過程中 thread 可以被 interrupted
    public void testLock() throws Exception {
        private final Lock lock = new ReentrantLock();
        lock.lock();
    
        Thread t = new Thread(new Runnable() {
    
            public void run() {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + " acquires the lock");
            }
        });
    
        t.start();
        Thread.sleep(3000);
        t.interrupt();//無作用,因 thread 並非在 wait,join,sleep 狀態
        Thread.sleep(Long.MAX_VALUE);
    }
    
    public void testLockInterruptibly() throws Exception {
        private final Lock lock = new ReentrantLock();
        lock.lock();
    
        Thread t = new Thread(new Runnable() {
    
            public void run() {
                try {
                    lock.lockInterruptibly();
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() + " interrupted.");
                }
            }
        });
    
        t.start();
        Thread.sleep(3000);
        t.interrupt();//thread 在等待獲得 lock 的過程中被中斷
        Thread.sleep(Long.MAX_VALUE);
    }
    
  • ReentrantReadWriteLock 讀寫鎖內部維護一對鎖, 一把用於讀取的操作, 另一把用於寫入操作, 讀取鎖可同時被多個讀取者擁有, 而此時鎖所保護的資料處於唯讀狀態, 讀寫鎖是互斥的, 因此當沒有任何讀取或寫入鎖定時, 才可以取得寫入鎖定, 但是對於讀取來說沒有互斥性,可參考 Java Doc 對於 Interface ReadWriteLock 的說明 , 另外使用 ReentrantReadWriteLock 時, 在讀取 thread 很多, 寫入 thread 很少的狀況下, 可能會使得寫入 thread 一直處於等待狀態, 因此必需留意 thread starvation 的問題

0 comments:

Post a Comment