Java顯式鎖

IT王小二發表於2021-06-18

Java 顯式鎖。

一、顯式鎖

什麼是顯式鎖?

由自己手動獲取鎖,然後手動釋放的鎖。

有了 synchronized(內建鎖) 為什麼還要 Lock(顯示鎖)?

使用 synchronized 關鍵字實現了鎖功能的,使用 synchronized 關鍵字將會隱式地獲取鎖,但是它將鎖的獲取和釋放固化了,也就是先獲取再釋放。

與內建加鎖機制不同的是,Lock 提供了一種無條件的、可輪詢的、定時的以及可中斷的鎖獲取操作,所有加鎖和解鎖的方法都是顯式的。

二、Lock的常用api

方法名稱 描述
void lock() 獲取鎖
void lockInterruptibly() throws InterruptedException 可中斷的獲取鎖,和lock()方法的不同之處在於該方法會響應中斷,即在鎖的獲取中可以中斷當前執行緒
boolean tryLock() 嘗試非阻塞的獲取鎖,呼叫該方法後立刻返回,如果能夠獲取則返回true,否則返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException 超時獲取鎖,當前執行緒會在以下三種情況下會返回:
1. 當前執行緒在超時時間內獲得了鎖
2.當前執行緒在超市時間內被中斷
3. 超時時間結束,返回false
void unlock(); 釋放鎖

三、Lock的標準用法

lock.lock();
try {
    // 業務邏輯
} finally {
    lock.unlock();
}
  • 在 finally 塊中釋放鎖,目的是保證在獲取到鎖之後,最終能夠被釋放。
  • 不要將獲取鎖的過程寫在 try 塊中,因為如果在獲取鎖(自定義鎖的實現)時發生了異常,異常丟擲的同時,也會導致鎖無故釋放。

四、ReentrantLock(可重入鎖)

Lock介面常用的實現類是 ReentrantLock。

示例程式碼:主執行緒100000次減,子執行緒10萬次加。

public class ReentrantLockTest {

    private Lock lock = new ReentrantLock();
    private int count = 0;

    public int getCount() {
        return count;
    }

    private static class ReentrantLockThread extends Thread {
        private ReentrantLockTest reentrantLockTest;

        public ReentrantLockThread(ReentrantLockTest reentrantLockTest) {
            this.reentrantLockTest = reentrantLockTest;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100000; i++) {
                reentrantLockTest.incr();
            }
            System.out.println(Thread.currentThread().getName() + " end, count =  " + reentrantLockTest.getCount());
        }
    }

    private void incr() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    private void decr() {
        lock.lock();
        try {
            count--;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockTest reentrantLockTest = new ReentrantLockTest();
        new ReentrantLockThread(reentrantLockTest).start();

        for (int i = 0; i < 100000; i++) {
            // 遞減100000
            reentrantLockTest.decr();
        }
        System.out.println(Thread.currentThread().getName() + " count =  " + reentrantLockTest.getCount());
    }
}

1. 鎖的可重入性

簡單地講就是:“同一個執行緒對於已經獲得到的鎖,可以多次繼續申請到該鎖的使用權”。而 synchronized 關鍵字隱式的支援重進入,比如一個 synchronized 修飾的遞迴方法,在方法執行時,執行執行緒在獲取了鎖之後仍能連續多次地獲得該鎖

同樣,ReentrantLock 在呼叫 lock()方法時,已經獲取到鎖的執行緒,能夠再次呼叫lock()方法獲取鎖而不被阻塞

2. 公平鎖和非公平鎖

  • 如果在時間上,先對鎖進行獲取的請求一定先被滿足,那麼這個鎖是公平的,反之,是不公平的。公平的獲取鎖,也就是等待時間最長的執行緒最優先獲取鎖,也可以說鎖獲取是順序的。
  • ReentrantLock 提供了一個建構函式,能夠控制鎖是否是公平的(預設為不公平鎖)。事實上,公平的鎖機制往往沒有非公平的效率高。
  • 在激烈競爭的情況下,非公平鎖的效能高於公平鎖的效能的一個原因是:在恢復一個被掛起的執行緒與該執行緒真正開始執行之間存在著嚴重的延遲。
  • 假設執行緒 A 持有一個鎖,並且執行緒 B 請求這個鎖,由於這個鎖已被執行緒 A 持有,因此 B 將被掛起。當 A 釋放鎖時,B 將被喚醒,因此會再次嘗試獲取鎖。與此同時,如果 C 也請求這個鎖,那麼 C 很可能會在 B 被完全喚醒之前獲得、使用以及釋放這個鎖,這樣的情況是一種“雙贏”的局面:B 獲得鎖的時刻並沒有推遲,C 更早地獲得了鎖,完成了自己的任務,然後釋放了鎖,並且吞吐量也獲得了提高。

五、ReentrantReadWriteLock(讀寫鎖)

ReentrantReadWriteLock 是 ReadWriteLock 的實現類。

之前提到鎖基本都是排他鎖,這些鎖在同一時刻只允許一個執行緒進行訪問,而讀寫鎖在同一時刻可以允許多個讀執行緒訪問但是在寫執行緒訪問時,所有的讀執行緒和其他寫執行緒均被阻塞。讀寫鎖維護了一對鎖,一個讀鎖和一個寫鎖,通過分離讀鎖和寫鎖,使得併發性相比一般的排他鎖有了很大提升。

讀鎖不排斥讀鎖,但是排斥寫鎖;寫鎖即排斥讀鎖也排斥寫鎖。

private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock getLock = lock.readLock(); // 讀鎖
private final Lock setLock = lock.writeLock(); // 寫鎖

至於上鎖、解鎖與 ReentrantLock 使用方式一致。

六、Condition

  • 任意一個 Java 物件,都擁有一組監視器方法(定義在 java.lang.Object 上),主要包括 wait()、wait(long timeout)、notify()以及 notifyAll()方法,這些方法與 synchronized 同步關鍵字配合,可以實現等待/通知模式。
  • Condition 介面也提供了類似 Object 的監視器方法,與 Lock 配合可以實現等待/通知模式。

常用api

方法名稱 描述
void await() throws InterruptedException 使當前執行緒進入等待狀態直到被通知(signal)或中斷
void signal() 喚醒一個等待的執行緒
void signalAll() 喚醒所有等待的執行緒

示例程式碼,主執行緒呼叫方法喚醒兩個子執行緒。

public class ConditionTest {

    private volatile boolean flag = false;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    private void task1() {
        lock.lock();
        try {
            try {
                System.out.println(Thread.currentThread().getName() + " 等待中");
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 等待結束");
            System.out.println("傳送郵件");
        } finally {
            lock.unlock();
        }
    }

    private void task2() {
        lock.lock();
        try {
            try {
                System.out.println(Thread.currentThread().getName() + " 等待中");
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 等待結束");
            System.out.println("傳送簡訊");
        } finally {
            lock.unlock();
        }
    }

    private void updateFlag() {
        lock.lock();
        try {
            this.flag = true;
            this.condition.signalAll();
        }finally {
            lock.unlock();
        }
    }

    private static class ConditionThread1 extends Thread {
        private ConditionTest conditionTest;
        public ConditionThread1(ConditionTest conditionTest) {
            this.conditionTest = conditionTest;
        }

        @Override
        public void run() {
            if (!conditionTest.flag) {
                conditionTest.task1();
            }
        }
    }

    private static class ConditionThread2 extends Thread {
        private ConditionTest conditionTest;
        public ConditionThread2(ConditionTest conditionTest) {
            this.conditionTest = conditionTest;
        }

        @Override
        public void run() {
            if (!conditionTest.flag) {
                conditionTest.task2();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ConditionTest conditionTest = new ConditionTest();
        new ConditionThread1(conditionTest).start();
        new ConditionThread2(conditionTest).start();
        Thread.sleep(1000);
        System.out.println("flag 改變。。。");
        conditionTest.updateFlag();
    }
}

都讀到這裡了,來個 點贊、評論、關注、收藏 吧!

文章作者:IT王小二
首發地址:https://www.itwxe.com/posts/4e06845c/
版權宣告:文章內容遵循 署名-非商業性使用-禁止演繹 4.0 國際 進行許可,轉載請在文章頁面明顯位置給出作者與原文連結。

相關文章