自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java鎖機制淺析:到底什么情況下該用ReentrantLock?

開發(fā) 后端
Java 提供了多種鎖機制來協(xié)調(diào)多個線程對共享資源的訪問。ReentrantLock 是最基本的一種鎖,它采用獨占式訪問方式,可以精確控制多個線程對共享資源的訪問順序。

在多線程編程中,鎖(Lock)是一種重要的同步機制,它可以保證同一時間只有一個線程可以訪問共享資源。Java 中提供了兩種類型的鎖:隱式鎖和顯式鎖。

隱式鎖通過 synchronized 關(guān)鍵字實現(xiàn),在使用時比較方便,但其粒度較大,無法滿足復(fù)雜的同步需求。而顯式鎖則通過 Lock 接口實現(xiàn),可以更靈活地控制鎖的粒度和行為。本文將介紹 Java 顯式鎖中的顯示鎖(ReentrantLock)和顯示條件隊列(Condition),并討論它們的使用方法、進(jìn)階用法以及可能遇到的問題和解決方案。

一、顯示鎖

1、簡介

顯示鎖(ReentrantLock)是 Java 顯式鎖中最常用的一種,它實現(xiàn)了 Lock 接口的所有特性,并提供了可重入和公平性等額外功能。其中,可重入指同一線程可以多次獲取該鎖而不會造成死鎖,公平性指多個線程按照申請鎖的順序獲得鎖。

與隱式鎖不同的是,顯示鎖需要手動加鎖和釋放鎖,通常使用 try-finally 語句塊保證鎖的正確釋放,避免異常導(dǎo)致鎖未能被及時釋放而造成死鎖。

2、基本使用

顯示鎖(ReentrantLock)的基本用法如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyRunnable implements Runnable {
    private final Lock lock = new ReentrantLock();
    private int count = 0;

    public void run() {
        lock.lock(); // 加鎖
        try {
            count++; // 訪問共享資源
        } finally {
            lock.unlock(); // 解鎖
        }
    }
}

在上述示例中,我們首先創(chuàng)建了一個 ReentrantLock 對象,并將其作為同步對象(Monitor)來訪問共享資源。然后,在訪問共享資源時使用 lock.lock() 方法加鎖,使用 lock.unlock() 方法解鎖。由于 lock 和 unlock 方法都可能拋出異常,因此通常需要使用 try-finally 語句塊來確保鎖的正確釋放。

3、可重入性

在 Java 中,可重入性指同一線程可以多次獲得該鎖而不會產(chǎn)生死鎖或排斥自己的情況。這是由于每個線程在加鎖時會記錄加鎖的次數(shù),只有在解鎖和加鎖次數(shù)相等時才真正釋放鎖。例如:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyRunnable implements Runnable {
    private final Lock lock = new ReentrantLock();
    private int count = 0;

    public void run() {
        lock.lock(); // 第一次加鎖
        try {
            count++; // 訪問共享資源
            lock.lock(); // 第二次加鎖
            try {
                count++; // 訪問共享資源
            } finally {
                lock.unlock(); // 第二次解鎖
            }
        } finally {
            lock.unlock(); // 第一次解鎖
        }
    }
}

在上述示例中,我們先后兩次獲取了同一個鎖,并在其中訪問了共享資源。由于鎖是可重入的,因此即使在第二次加鎖時仍然持有鎖,也不會產(chǎn)生死鎖或排斥自己的情況。

4、公平性

在 Java 中,公平性指多個線程按照申請鎖的順序獲得鎖的特性。公平性可以避免某些線程長期持有鎖,導(dǎo)致其他線程無法獲得鎖而等待過長時間的情況。

在顯示鎖中,默認(rèn)情況下是非公平的,即當(dāng)前線程可以隨時獲得鎖,而不考慮其他線程的申請順序。這樣可能會導(dǎo)致某些線程一直無法獲得鎖,從而產(chǎn)生線程饑餓(Thread Starvation)的問題。

為了解決這個問題,Java 中提供了公平鎖(FairLock),它會按照線程申請鎖的順序進(jìn)行排隊,并且保證先來先得的原則。示例代碼如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyRunnable implements Runnable {
    private final Lock lock = new ReentrantLock(true); // 公平鎖
    private int count = 0;

    public void run() {
        lock.lock(); // 加鎖
        try {
            count++; // 訪問共享資源
        } finally {
            lock.unlock(); // 解鎖
        }
    }
}

在上述示例中,我們創(chuàng)建了一個公平鎖(FairLock),并將其傳遞給 ReentrantLock 的構(gòu)造函數(shù)中。然后,在訪問共享資源時使用 lock.lock() 方法加鎖,使用 lock.unlock() 方法解鎖。由于公平鎖會按照線程申請鎖的順序進(jìn)行排隊,因此可以避免線程饑餓的問題。

二、顯示條件隊列

1、簡介

條件隊列(Condition)是 Java 顯式鎖中實現(xiàn)線程等待/通知機制的一種方式。它允許多個線程在某些條件不滿足時暫停執(zhí)行,并在特定條件滿足時恢復(fù)執(zhí)行。與 synchronized 關(guān)鍵字相比,條件隊列提供了更靈活和細(xì)粒度的同步控制,可以更好地支持復(fù)雜的同步需求。

條件隊列通常與顯示鎖一起使用,通過
ReentrantLock.newCondition() 方法創(chuàng)建一個 Condition 對象,并使用 await()、signal() 和 signalAll() 等方法來進(jìn)行線程等待和喚醒操作。其中,await() 方法用于使當(dāng)前線程等待某個條件發(fā)生變化,signal() 方法用于喚醒一個等待該條件的線程,signalAll() 方法用于喚醒所有等待該條件的線程。

2、基本使用

顯示條件隊列(Condition)的基本用法如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyRunnable implements Runnable {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private boolean flag = false;

    public void run() {
        lock.lock(); // 加鎖
        try {
            while (!flag) {
                condition.await(); // 等待條件變化
            }
            // 訪問共享資源
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock(); // 解鎖
        }
    }

    public void changeFlag() {
        lock.lock(); // 加鎖
        try {
            flag = true; // 修改條件
            condition.signalAll(); // 喚醒等待的線程
        } finally {
            lock.unlock(); // 解鎖
        }
    }
}

在上述示例中,我們首先創(chuàng)建了一個 Condition 對象,并將其關(guān)聯(lián)到一個顯示鎖(ReentrantLock)上。然后,在訪問共享資源時使用 while 循環(huán)判斷條件是否滿足,如果不滿足則調(diào)用 condition.await() 方法使當(dāng)前線程進(jìn)入等待狀態(tài)。在修改條件時調(diào)用 changeFlag() 方法,并使用 condition.signalAll() 喚醒所有等待該條件的線程。需要注意的是,await() 方法和 signal()/signalAll() 方法都必須在鎖保護(hù)下進(jìn)行調(diào)用,否則會拋出
IllegalMonitorStateException 異常。

3、進(jìn)階使用

條件隊列(Condition)還提供了許多高級操作,用于支持更復(fù)雜的同步需求。以下是一些常用的進(jìn)階使用方式:

(1)等待超時

有時候我們希望線程在等待一段時間后自動喚醒,而不是一直等待到被喚醒為止。這時候可以使用 condition.await(long time, TimeUnit unit) 方法,它允許我們指定等待的最長時間,如果超過指定時間仍未被喚醒,則自動退出等待狀態(tài)。示例代碼如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class MyRunnable implements Runnable {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private boolean flag = false;

    public void run() {
        lock.lock(); // 加鎖
        try {
            long timeout = 10L; // 等待 10 秒
            while (!flag) {
                if (!condition.await(timeout, TimeUnit.SECONDS)) {
                    // 在等待一定時間后還未被喚醒,做相應(yīng)處理
                    break;
                }
            }
            // 訪問共享資源
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock(); // 解鎖
        }
    }

    public void changeFlag() {
        lock.lock(); // 加鎖
        try {
            flag = true; // 修改條件
            condition.signalAll(); // 喚醒等待的線程
        } finally {
            lock.unlock(); // 解鎖
        }
    }
}

在上述示例中,我們使用 condition.await(timeout, TimeUnit.SECONDS) 方法等待了 10 秒,如果超過該時間還未被喚醒,則退出等待狀態(tài)并做相應(yīng)處理。

(2)等待多個條件

有時候我們需要等待多個條件同時滿足后才能繼續(xù)執(zhí)行,這時候可以使用多個條件隊列(Condition)來實現(xiàn)。示例代碼如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyRunnable implements Runnable {
    private final Lock lock = new ReentrantLock();
    private final Condition condition1 = lock.newCondition();
    private final Condition condition2 = lock.newCondition();
    private boolean flag1 = false;
    private boolean flag2 = false;

    public void run() {
        lock.lock(); // 加鎖
        try {
            while (!flag1 || !flag2) {
                if (!flag1) {
                    condition1.await(); // 等待條件 1
                }
                if (!flag2) {
                    condition2.await(); // 等待條件 2
                }
            }
            // 訪問共享資源
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock(); // 解鎖
        }
    }

    public void changeFlag1() {
        lock.lock(); // 加鎖
        try {
            flag1 = true; // 修改條件 1
            condition1.signalAll(); // 喚醒等待條件 1 的線程
        } finally {
            lock.unlock(); // 解鎖
        }
    }

    public void changeFlag2() {
        lock.lock(); // 加鎖
        try {
            flag2 = true; // 修改條件 2
            condition2.signalAll(); // 喚醒等待條件 2 的線程
        } finally {
            lock.unlock(); // 解鎖
        }
    }
}

在上述示例中,我們創(chuàng)建了兩個條件隊列(Condition),分別用于等待兩個不同的條件。然后,在訪問共享資源時使用 while 循環(huán)判斷兩個條件是否都滿足,如果不滿足則分別調(diào)用 condition1.await() 和 condition2.await() 方法使當(dāng)前線程進(jìn)入等待狀態(tài)。在修改條件時分別調(diào)用 changeFlag1() 和 changeFlag2() 方法,并使用 condition1.signalAll() 和 condition2.signalAll() 喚醒等待相應(yīng)條件的線程。

(3)實現(xiàn)生產(chǎn)者消費者模型

條件隊列(Condition)還可以用于實現(xiàn)生產(chǎn)者消費者模型,其中生產(chǎn)者和消費者共享一個緩沖區(qū),當(dāng)緩沖區(qū)為空時,消費者需要等待生產(chǎn)者生產(chǎn)數(shù)據(jù);當(dāng)緩沖區(qū)滿時,生產(chǎn)者需要等待消費者消費數(shù)據(jù)。示例代碼如下:

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyRunnable implements Runnable {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private final Queue<Integer> queue = new LinkedList<>();
    private final int maxSize = 10;

    public void run() {
        while (true) {
            lock.lock(); // 加鎖
            try {
                while (queue.isEmpty()) {
                    notEmpty.await(); // 等待不為空
                }
                int data = queue.poll(); // 取出數(shù)據(jù)
                notFull.signalAll(); // 喚醒生產(chǎn)者
                // 處理數(shù)據(jù)
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock(); // 解鎖
            }
        }
    }

    public void produce(int data) {
        lock.lock(); // 加鎖
        try {
            while (queue.size() == maxSize) {
                notFull.await(); // 等待不滿
            }
            queue.offer(data); // 添加數(shù)據(jù)
            notEmpty.signalAll(); // 喚醒消費者
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock(); // 解鎖
        }
    }
}

在上述示例中,我們創(chuàng)建了一個緩沖區(qū)(Queue),并使用兩個條件隊列(Condition)分別表示緩沖區(qū)不為空和不滿。在消費者線程中,使用 while 循環(huán)判斷緩沖區(qū)是否為空,如果為空則調(diào)用 notEmpty.await() 方法使當(dāng)前線程進(jìn)入等待狀態(tài)。當(dāng)從緩沖區(qū)取出數(shù)據(jù)后,調(diào)用 notFull.signalAll() 方法喚醒所有等待不滿的生產(chǎn)者線程。在生產(chǎn)者線程中,使用 while 循環(huán)判斷緩沖區(qū)是否已滿,如果已滿則調(diào)用 notFull.await() 方法使當(dāng)前線程進(jìn)入等待狀態(tài)。當(dāng)往緩沖區(qū)添加數(shù)據(jù)后,調(diào)用 notEmpty.signalAll() 方法喚醒所有等待不為空的消費者線程。

三、讀寫鎖

1、簡介

讀寫鎖是一種特殊的鎖,它允許多個線程同時讀取共享資源,但只允許一個線程對共享資源進(jìn)行寫操作。讀寫鎖可以有效地提高并發(fā)性能,特別是在讀取操作遠(yuǎn)多于寫操作的場景下。

Java 中提供了 ReentrantReadWriteLock 類來實現(xiàn)讀寫鎖。它包含一個讀鎖和一個寫鎖,讀鎖可同時被多個線程持有,但寫鎖一次只能被一個線程持有。示例代碼如下:

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class MyRunnable implements Runnable {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private int count = 0;

    public void run() {
        lock.readLock().lock(); // 獲取讀鎖
        try {
            // 訪問共享資源(讀取)
        } finally {
            lock.readLock().unlock(); // 釋放讀鎖
        }
    }

    public void write() {
        lock.writeLock().lock(); // 獲取寫鎖
        try {
        		// 訪問共享資源(寫入)
        } finally {
       		 lock.writeLock().unlock(); // 釋放寫鎖
        }
    }
}

在上述示例中,我們創(chuàng)建了一個讀寫鎖(ReentrantReadWriteLock),并使用 readLock() 方法獲取讀鎖,writeLock() 方法獲取寫鎖。在訪問共享資源時,讀取操作可以同時被多個線程持有讀鎖,而寫入操作必須先獲取寫鎖,然后其他所有操作都被阻塞,直到寫入完成并釋放寫鎖。

2、使用場景

讀寫鎖適用于以下場景:

  • 讀取操作遠(yuǎn)多于寫入操作。
  • 共享資源的狀態(tài)不會發(fā)生太大變化,即讀取操作和寫入操作之間的時間間隔較長。
  • 寫入操作對資源的一致性要求高,需要獨占式訪問。

使用讀寫鎖可以有效地提高程序的并發(fā)性能,特別是在讀取操作遠(yuǎn)多于寫入操作的情況下。但需要注意的是,讀寫鎖的實現(xiàn)需要消耗更多的系統(tǒng)資源,因此只有在讀取操作遠(yuǎn)多于寫入操作、且讀寫操作之間的時間間隔較長時才應(yīng)該使用讀寫鎖。

四、StampedLock

1、簡介

StampedLock 是 Java 8 新增的一種鎖機制,它是對讀寫鎖的一種改進(jìn),具有更高的并發(fā)性能。StampedLock 支持三種模式:讀(共享)、寫(獨占)和樂觀讀(非獨占)。與 ReadWriteLock 不同的是,StampedLock 的讀取操作不會被阻塞,但可能會失敗,如果讀取的數(shù)據(jù)在讀取過程中發(fā)生了改變,則讀取操作會失敗并返回一個標(biāo)記(stamp),此時可以根據(jù)需要重試讀取操作或者轉(zhuǎn)換為獨占寫入操作。

StampedLock 使用一個長整型的 stamp 來表示鎖的版本號,每次修改數(shù)據(jù)后都會更新版本號。讀取操作需要傳入當(dāng)前版本號以確保讀取的數(shù)據(jù)沒有被修改,寫入操作則需要傳入上一次讀取操作返回的版本號以確保數(shù)據(jù)的一致性。示例代碼如下:

import java.util.concurrent.locks.StampedLock;
public class MyRunnable implements Runnable {
    private final StampedLock lock = new StampedLock();
    private int x = 0;
    private int y = 0;

    public void run() {
        long stamp = lock.tryOptimisticRead(); // 嘗試樂觀讀取
        int currentX = x;
        int currentY = y;
        if (!lock.validate(stamp)) { // 校驗版本號
            stamp = lock.readLock(); // 獲取讀鎖
            try {
                currentX = x; // 重新讀取數(shù)據(jù)
                currentY = y;
            } finally {
                lock.unlockRead(stamp); // 釋放讀鎖
            }
        }
        // 訪問共享資源(讀?。?    }

    public void write(int newX, int newY) {
        long stamp = lock.writeLock(); // 獲取寫鎖
        try {
            x = newX; // 修改數(shù)據(jù)
            y = newY;
        } finally {
            lock.unlockWrite(stamp); // 釋放寫鎖
        }
    }
}

在上述示例中,我們創(chuàng)建了一個 StampedLock,并使用 tryOptimisticRead() 方法嘗試進(jìn)行樂觀讀取操作。如果校驗版本號失敗,則說明數(shù)據(jù)被修改過,此時需要再次獲取讀鎖并重新讀取數(shù)據(jù)。在修改數(shù)據(jù)時,使用 writeLock() 方法獲取寫鎖,修改完成后釋放寫鎖。

2、使用場景

StampedLock 適用于以下場景:

  • 讀取操作頻繁,而寫入操作較少。
  • 數(shù)據(jù)的一致性要求不高,即數(shù)據(jù)會發(fā)生周期

性的變化,但讀取操作與寫入操作之間的時間間隔較短,不需要使用分布式鎖或者數(shù)據(jù)庫事務(wù)來保證數(shù)據(jù)一致性。

使用 StampedLock 可以提高程序的并發(fā)性能,特別是在讀取操作頻繁、寫入操作較少的情況下。但需要注意的是,StampedLock 的實現(xiàn)依賴于硬件的 CAS(Compare and Swap)指令,因此在某些 CPU 架構(gòu)上可能會存在性能問題。此外,在使用樂觀讀取模式時需要進(jìn)行版本號校驗,如果校驗失敗則需要重新獲取讀鎖并重新讀取數(shù)據(jù),這可能會帶來額外的開銷和復(fù)雜度。

五、總結(jié)

Java 提供了多種鎖機制來協(xié)調(diào)多個線程對共享資源的訪問。ReentrantLock 是最基本的一種鎖,它采用獨占式訪問方式,可以精確控制多個線程對共享資源的訪問順序。Condition 可以用于在鎖的基礎(chǔ)上實現(xiàn)更靈活的同步操作,例如線程的等待和喚醒。ReadWriteLock 是一種特殊的鎖,它允許多個線程同時讀取共享資源,但只允許一個線程對共享資源進(jìn)行寫操作。StampedLock 是對讀寫鎖的一種改進(jìn),具有更高的并發(fā)性能,但需要注意的是它的實現(xiàn)依賴于硬件的 CAS 指令。

在使用鎖時需要注意避免死鎖、避免過度競爭和防止資源饑餓等問題。應(yīng)該根據(jù)具體的場景選擇不同的鎖機制,并合理地設(shè)置鎖的粒度和范圍。同時也可以考慮使用一些高級的并發(fā)工具來簡化鎖的管理,例如 Executor 框架、原子變量、信號量、倒計時門閂等。

責(zé)任編輯:姜華 來源: 今日頭條
相關(guān)推薦

2023-11-23 23:52:06

options請求瀏覽器

2015-06-01 06:39:18

JavaJava比C++

2013-09-12 10:41:39

VDI部署

2015-06-29 14:23:13

JavaC++慢很多

2013-07-29 14:50:43

API

2020-11-18 09:26:52

@property裝飾器代碼

2013-09-23 10:05:50

2010-07-13 16:07:26

SQL Server行

2014-11-03 09:52:25

DNSUDPTCP

2024-01-09 11:39:47

數(shù)字化轉(zhuǎn)型數(shù)字優(yōu)先企業(yè)

2021-06-04 09:17:13

JavaScriptBoolean函數(shù)

2009-03-05 10:55:00

企業(yè)無線Wi-Fi

2021-09-14 07:26:25

雪花算法ID

2011-12-11 11:51:28

2010-04-14 17:46:10

Oracle數(shù)據(jù)庫

2024-11-07 12:08:27

微服務(wù)協(xié)議通信

2020-09-24 09:43:59

Http協(xié)議options請求

2022-07-20 08:07:21

數(shù)據(jù)庫分布式數(shù)據(jù)庫

2013-09-04 15:17:38

2011-12-28 15:24:21

點贊
收藏

51CTO技術(shù)棧公眾號