并發(fā)學(xué)習(xí)之 - ReentrantLock

synchronized 和 ReentrantLock

上一篇文章 并發(fā)學(xué)習(xí)之 - synchronized 中我們講解了如何使用關(guān)鍵字 synchronized 來實現(xiàn)同步訪問。從 Java 5 之后,JDK 提供了另外一種方式來實現(xiàn)同步訪問,那就是 ReentrantLock。ReentrantLock 增加了一些高級功能,主要是這 3 項:等待可中斷、可實現(xiàn)公平鎖,以及鎖可以綁定多個條件。

在 JDK 6 之前,盡管 synchronized 關(guān)鍵字實現(xiàn)同步很方便,但是這種同步操作很重量級,很大程度上影響程序的執(zhí)行效率,所以對于開發(fā)者來說,使用起來會有點畏懼。但是在 JDK 6 之后,對 synchronized 進行了很大的優(yōu)化, 引入了偏向鎖,適應(yīng)性自旋,輕量級鎖和重量級鎖,鎖粗化等手段,目前 synchronized 和 ReentrantLock 的性能基本能夠持平,所以一般情況下,還是建議使用 synchronized 關(guān)鍵字,會使得程序簡潔和易讀。

JDK 6 之前

synchronized_jdk5_before.jpeg

JDK 6 之后

synchronized_jdk5_later.jpeg

但是 ReentrantLock 也有很多優(yōu)點,ReentrantLock 是 Lock 的實現(xiàn)類,是一個互斥的同步器,在多線程高競爭條件下,ReentrantLock 比 synchronized 有更加優(yōu)異的性能表現(xiàn)。

(1) Lock 使用起來比較靈活,但是必須有釋放鎖的配合動作
Lock 必須手動獲取與釋放鎖,而 synchronized 不需要手動釋放和開啟鎖 Lock 只適用于代碼塊鎖,而 synchronized 可用于修飾方法、代碼塊等。

(2) ReentrantLock的優(yōu)勢體現(xiàn)在:
具備嘗試非阻塞地獲取鎖的特性:當(dāng)前線程嘗試獲取鎖,如果這一時刻鎖沒有被其他線程獲取到,則成功獲取并持有鎖
能被中斷地獲取鎖的特性:與synchronized不同,獲取到鎖的線程能夠響應(yīng)中斷,當(dāng)獲取到鎖的線程被中斷時,中斷異常將會被拋出,同時鎖會被釋放
超時獲取鎖的特性:在指定的時間范圍內(nèi)獲取鎖;如果截止時間到了仍然無法獲取鎖,則返回

(3) 在使用 ReentrantLock 時,要注意:在 finally 中釋放鎖,目的是保證在獲取鎖之后,最終能夠被釋放
不要將獲取鎖的過程寫在 try 塊內(nèi),因為如果在獲取鎖時發(fā)生了異常,異常拋出的同時,也會導(dǎo)致鎖無法被釋放。

(4) ReentrantLock 提供了一個 newCondition 的方法,以便用戶在同一鎖的情況下可以根據(jù)不同的情況執(zhí)行等待或喚醒的動作。

2. ReentrantLock 用法

ReentrantLock 是 Lock 接口的實現(xiàn)類,Lock 提供了以下幾個方法:

// 獲取鎖,獲取過程中不可中斷
void lock();

// 獲取鎖,獲取過程中可中斷,中斷后拋出 InterruptedException 異常
void lockInterruptibly() throws InterruptedException;

// 非阻塞獲取鎖,無論是否獲取到均返回,true 獲取成功,false 獲取失敗
boolean tryLock();

// 嘗試獲取鎖,在 time 時間內(nèi)獲取到返回 true,超時則返回 false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

// 釋放鎖,一般在 finally 塊中
void unlock();

// 創(chuàng)建等待條件
Condition newCondition();

2.1 Lock 鎖使用

ReentrantLock 鎖使用起來也比較簡單,主要根據(jù)場景來進行選擇,有一點需要注意,如果不是使用 tryLock 的方法,需要在 finally 塊中釋放鎖。下面給出幾種獲取鎖的使用示例代碼。

public class ReentrantLockTest {

    private static final ReentrantLock reentrantLock = new ReentrantLock();
    private static int sCount = 0;

    /**
     * lock
     */
    private static class MyRunnable implements Runnable {

        @Override
        public void run() {
            reentrantLock.lock();
            try {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + " --- > " + sCount++);
                }
                Thread.sleep(3500);
            } catch (Exception e) {

            } finally {
                reentrantLock.unlock();
            }

        }
    }

    /**
     * tryLock
     */
    private static class MyRunnable1 implements Runnable {

        @Override
        public void run() {
            boolean isLock = reentrantLock.tryLock();
            if (isLock) {
                execute();
            } else {
                System.out.println(Thread.currentThread().getName() + " --- > 獲取鎖【失敗】");
            }
        }
    }

    /**
     * tryLock(long timeout, TimeUnit unit),超時獲取失敗,中間可以被中斷
     */
    private static class MyRunnable2 implements Runnable {

        @Override
        public void run() {
            boolean isLock;
            try {
//                isLock = reentrantLock.tryLock(3, TimeUnit.SECONDS);
                isLock = reentrantLock.tryLock(4, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " --- > 獲取鎖【被中斷】");
                return;
            }
            if (isLock) {
                execute();
            } else {
                System.out.println(Thread.currentThread().getName() + " --- > 獲取鎖【失敗】");
            }
        }
    }

    /**
     * lockInterruptibly 中間可以被中斷,如果不中斷就一直嘗試獲取鎖
     */
    private static class MyRunnable3 implements Runnable {

        @Override
        public void run() {
            try {
                reentrantLock.lockInterruptibly();
                execute();
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " --- > 獲取鎖【被中斷】");
            }
        }
    }

    private static void execute() {
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " --- > " + sCount++);
            }
        } catch (Exception e) {

        } finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        sCount = 0;
        // lock
//        test1();
        // tryLock
//        test2();
        // tryLock(long timeout, TimeUnit unit)
//        test3();
        // lockInterruptibly
        test4();
    }

    private static void test1() {
        MyRunnable runnable = new MyRunnable();
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);

        thread1.start();
        thread2.start();
    }

    private static void test2() {
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable1());

        thread1.start();
        thread2.start();
    }

    private static void test3() {
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable2());

        thread1.start();
        thread2.start();

        // 中斷測試
        thread1.interrupt();
        thread2.interrupt();
    }

    private static void test4() throws InterruptedException {
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable3());

        thread1.start();
        thread2.start();

        Thread.sleep(2000);
        // 中斷測試
        thread1.interrupt();
        thread2.interrupt();
    }
}

2.2 Condition 使用

對應(yīng)一個 ReentrantLock 鎖,可以有多個條件,這是 synchronized 不具備的靈活性。synchronized 中方法或者塊中,只能使用鎖的 wait() 和 notify()、notifyAll()。而 ReentrantLock 的一個鎖可以有多個條件,ReentrantLock 的一個鎖對應(yīng)一個同步隊列,多個條件對應(yīng)多個等待隊列。下面使用 ReentrantLock 的條件實現(xiàn)生產(chǎn)者 - 消費者模型。

public class ProducerConsumer1 {

    private static final ReentrantLock sLock = new ReentrantLock();
    private static final Condition sProducerCondition = sLock.newCondition();
    private static final Condition sConsumerCondition = sLock.newCondition();

    private static final int MAX = 10;
    private static int count = 0;

    private static class Producer implements Runnable {

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                sLock.lock();
                try {
                    if (count == MAX) {
                        sProducerCondition.await();
                    }
                    count++;
                    System.out.println("生產(chǎn)者 ---- > " + count);
                    Thread.sleep(500);
                    sConsumerCondition.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    sLock.unlock();
                }
            }
        }
    }

    private static class Consumer implements Runnable {

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                sLock.lock();
                try {
                    if (count == 0) {
                        sConsumerCondition.await();
                    }
                    count--;
                    System.out.println("消費者 ---- > " + count);
                    Thread.sleep(300);
                    sProducerCondition.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    sLock.unlock();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread producer = new Thread(new Producer());
        Thread consumer = new Thread(new Consumer());
        producer.start();
        consumer.start();

        Thread.sleep(10000);
    }
}

2.3 其他說明

ReetrantLock 和 synchronized 都是一種支持重進入的鎖,即該鎖可以支持一個線程對資源重復(fù)加鎖。但是 synchronized 不支持公平鎖,ReetrantLock 支持公平鎖與非公平鎖。公平與非公平指的是在請求先后順序上,先對鎖進行請求的就一定先獲取到鎖,那么這就是公平鎖,反之,如果對于鎖的獲取并沒有時間上的先后順序,如后請求的線程可能先獲取到鎖,這就是非公平鎖,一般而言非,非公平鎖機制的效率往往會勝過公平鎖的機制,但在某些場景下,可能更注重時間先后順序,那么公平鎖自然是很好的選擇。需要注意的是 ReetrantLock 支持對同一線程重加鎖,但是加鎖多少次,就必須解鎖多少次,這樣才可以成功釋放鎖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容