併發編程之ReentrantLock

正文

ReentrantLock 是Java併發包中提供的一個可重入的互斥鎖。ReentrantLock 和 synchronized 在基本用法和行爲語義上基本相同,一樣具備可重入性。只是 ReentrantLock 增長了一些高級擴展功能,好比,能夠實現公平鎖,同時也能夠實現綁定多個 Condition。併發

可重入性 / 公平鎖 / 非公平鎖

  1. 可重入性: 一個線程在獲取一段使用鎖的代碼時,能夠再次進入這段代碼。例如,一個被可重入鎖修飾的遞歸程序,能夠重複的獲取鎖,而不會出現把本身鎖死的狀況。synchronized 和 ReentrantLock 都具備可重入性。ide

  2. 公平鎖 / 非公平鎖
    所謂公平鎖,指鎖的獲取策略相對公平,當多個線程在獲取同一個鎖時,必須按照鎖的申請時間來依次得到鎖;非公平鎖則不一樣。synchronized 是非公平鎖,ReentrantLock 默認也是非公平的,可是能夠經過帶 boolean 參數的構造方法指定使用公平鎖,但非公平鎖的性能通常要優於公平鎖。
    synchronized 是 Java 原生的互斥同步鎖,使用方便,對於 synchronized 修飾的方法或同步塊,無需再顯式釋放鎖。而 ReentrantLock 作爲 API 層面的互斥鎖,須要顯式地去加鎖解鎖。採用 Lock ,必須主動去釋放鎖,而且在發生異常時,不會自動釋放鎖。所以通常來講,使用 Lock 必須在 try{}catch{} 塊中進行,而且將釋放鎖的操做放在 finally 塊中進行,以保證鎖必定被被釋放,防止死鎖的發生。函數

class Demo{

    private final ReentrantLock lock = new ReentrantLock();

    public void method(){

        lock.lock(); // 加鎖

        try{
            // to do...

        }finally {
            lock.unlock(); // 釋放鎖
        }
    }
}

源碼分析

  1. 無參構造函數,默認是非公平鎖
/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }
  1. 帶 Boolean 參數的構造器
/**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
  1. lock()
public void lock() {
        sync.acquire(1); //代理到Sync的lock方法上
    }

Sync的lock方法是抽象的,實際的lock會代理到FairSync或是NonFairSync上(根據用戶的選擇來決定,公平鎖仍是非公平鎖)oop

  1. unlock()
public void unlock() {
        sync.release(1);
    }

釋放鎖,調用sync的release方法。源碼分析

  1. tryLock()
public void method(){

        if(lock.tryLock()){

            try{

                // to do...

            }finally {
                lock.unlock();
            }
        }else {

            // 若是不能獲取鎖,則作其餘事情
        }
    }

tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,若是獲取成功,則返回true,若是獲取失敗(即鎖已被其餘線程獲取),則返回false。性能

  1. newCondition()
public Condition newCondition() {
        return sync.newCondition();
}

獲取一個conditon,ReentrantLock支持多個Conditionui

示例

/*
* 編寫一個程序,開啓 3 個線程,這三個線程的 ID 分別爲 A、B、C,每一個線程將本身的 ID 在屏幕上打印 10 遍,要求輸出的結果必須按順序顯示。
*    如:ABCABCABC…… 依次遞歸
*/
class AlternateDemo{

    private int num = 1; // 標記當前正在執行的線程

    private ReentrantLock lock = new ReentrantLock();

    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    /**
     *
     * @param totalLoops 循環第幾輪
     */
    public void loopA(int totalLoops){

        lock.lock();

        try{
            if(num != 1){
                condition1.await();
            }

            System.out.println(Thread.currentThread().getName() + "\t" + totalLoops);

            num = 2;
            condition2.signal();

        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }

    public void loopB(int totalLoops){

        lock.lock();

        try{
            if(num != 2){
                condition2.await();
            }

            System.out.println(Thread.currentThread().getName() + "\t" + totalLoops);

            num = 3;
            condition3.signal();

        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }

    public void loopC(int totalLoops){

        lock.lock();

        try{
            if(num != 3){
                condition3.await();
            }

            System.out.println(Thread.currentThread().getName() + "\t" + totalLoops);

            num = 1;
            condition1.signal();

        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }
}


public class Main {

    public static void main(String[] args) {

        AlternateDemo alternateDemo = new AlternateDemo();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    alternateDemo.loopA(i);
                }
            }
        }, "A").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    alternateDemo.loopB(i);
                }
            }
        }, "B").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    alternateDemo.loopC(i);
                    System.out.println("----------------------");
                }
            }
        }, "C").start();
    }
}

代碼分析:三個線程A、B、C分別調用10次打印,只需想辦法控制三個現成的執行順序。
若線程A先獲取鎖,直接打印。
若線程B先獲取鎖,B會被阻塞,釋放鎖後由A、C爭奪。
若線程C先獲取鎖,C會被阻塞,釋放鎖後由A、B爭奪。
使用一個變量來維持獲取鎖的順序,分別是線程A、線程B、線程Cthis

總結

  1. Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現;
  2. synchronized在發生異常時,會自動釋放線程佔有的鎖,所以不會致使死鎖現象發生;而Lock在發生異常時,若是沒有主動經過unLock()去釋放鎖,則極可能形成死鎖現象,所以使用Lock時須要在finally塊中釋放鎖;
  3. Lock類能夠建立Condition對象,Condition對象用來是線程等待和喚醒線程,須要注意的是Condition對象的喚醒的是用同一個Condition執行await方法的線程,因此也就能夠實現喚醒指定類的線程
相關文章
相關標籤/搜索