成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

StampedLock,一種比讀寫鎖更快的鎖!

開發 前端
與ReadWriteLock?相比,StampedLock進一步把讀鎖細分為樂觀讀和悲觀讀,能進一步提升了并發執行效率。

01、背景介紹

在上一篇文章中,我們講到了使用ReadWriteLock可以解決多線程同時讀,但只有一個線程能寫的問題。

如果繼續深入的分析ReadWriteLock,從鎖的角度分析,會發現它有一個潛在的問題:如果有線程正在讀數據,寫線程準備修改數據的時候,需要等待讀線程釋放鎖后才能獲取寫鎖,簡單的說就是,讀的過程中不允許寫,這其實是一種悲觀的讀鎖。

為了進一步的提升程序并發執行效率,Java 8 引入了一個新的讀寫鎖:StampedLock。

與ReadWriteLock相比,StampedLock最大的改進點在于:在原先讀寫鎖的基礎上,新增了一種叫樂觀讀的模式。該模式并不會加鎖,因此不會阻塞線程,程序會有更高的執行效率。

什么是樂觀鎖和悲觀鎖呢?

  • 樂觀鎖:就是樂觀的估計讀的過程中大概率不會有寫入,因此被稱為樂觀鎖
  • 悲觀鎖:指的是讀的過程中拒絕有寫入,也就是寫入必須等待

顯然樂觀鎖的并發執行效率會更高,但一旦有數據的寫入導致讀取的數據不一致,需要能檢測出來,再讀一遍就行。

下面我們一起來了解一下StampedLock的用法!

02、StampedLock 用法介紹

StampedLock的使用方式比較簡單,只需要實例化一個StampedLock對象,然后調用對應的讀寫方法即可,它有三個核心方法如下!

  • readLock():表示讀鎖,多個線程讀不會阻塞,效果與ReadWriteLock的讀鎖模式類似
  • writeLock():表示寫鎖,同一時刻有且只有一個寫線程能獲取鎖資源,效果與ReadWriteLock的寫鎖模式類似
  • tryOptimisticRead():表示樂觀讀,并沒有加鎖,它用于非常短的讀操作,允許多個線程同時讀

其中readLock()和writeLock()方法,與ReadWriteLock的效果完全一致,在此就不重復演示了。

下面我們來看一個tryOptimisticRead()方法的簡單使用示例。

2.1、tryOptimisticRead 方法

public class CounterDemo {

    private final StampedLock lock = new StampedLock();

    private int count;

    public void write() {
        // 1.獲取寫鎖
        long stamp = lock.writeLock();
        try {
            count++;
            // 方便演示,休眠一下
            sleep(200);
            println("獲得了寫鎖,count:" + count);
        } finally {
            // 2.釋放寫鎖
            lock.unlockWrite(stamp);
        }
    }

    public int read() {
        // 1.嘗試通過樂觀讀模式讀取數據,非阻塞
        long stamp = lock.tryOptimisticRead();
        // 2.假設x = 0,但是x可能被寫線程修改為1
        int x = count;
        // 方便演示,休眠一下
        int millis = new Random().nextInt(500);
        sleep(millis);
        println("通過樂觀讀模式讀取數據,value:" + x + ", 耗時:" + millis);
        // 3.檢查樂觀讀后是否有其他寫鎖發生
        if(!lock.validate(stamp)){
            // 4.如果有,采用悲觀讀鎖,并重新讀取數據到當前線程局部變量
            stamp = lock.readLock();
            try {
                x = count;
                println("樂觀讀后檢查到數據發生變化,獲得了讀鎖,value:" + x);
            } finally{
                // 5.釋放悲觀讀鎖
                lock.unlockRead(stamp);
            }
        }
        // 6.返回讀取的數據
        return x;
    }


    private void sleep(long millis){
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    private void println(String message){
        String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
        System.out.println(time + " 線程:" + Thread.currentThread().getName() + " " + message);
    }
}
public class MyThreadTest {

    public static void main(String[] args) throws InterruptedException {
        CounterDemo counter = new CounterDemo();
        Runnable readRunnable = new Runnable() {
            @Override
            public void run() {
                counter.read();
            }
        };
        Runnable writeRunnable = new Runnable() {
            @Override
            public void run() {
                counter.write();
            }
        };
        // 啟動3個讀線程
        for (int i = 0; i < 3; i++) {
            new Thread(readRunnable).start();
        }
        // 停頓一下
        Thread.sleep(300);
        // 啟動3個寫線程
        for (int i = 0; i < 3; i++) {
            new Thread(writeRunnable).start();
        }
    }
}

看一下運行結果:

2023-10-25 13:47:16:952 線程:Thread-0 通過樂觀讀模式讀取數據,value:0, 耗時:19
2023-10-25 13:47:17:050 線程:Thread-2 通過樂觀讀模式讀取數據,value:0, 耗時:172
2023-10-25 13:47:17:247 線程:Thread-1 通過樂觀讀模式讀取數據,value:0, 耗時:369
2023-10-25 13:47:17:382 線程:Thread-3 獲得了寫鎖,count:1
2023-10-25 13:47:17:586 線程:Thread-4 獲得了寫鎖,count:2
2023-10-25 13:47:17:788 線程:Thread-5 獲得了寫鎖,count:3
2023-10-25 13:47:17:788 線程:Thread-1 樂觀讀后檢查到數據發生變化,獲得了讀鎖,value:3

從日志上可以分析得出,讀線程Thread-0和Thread-2在啟動寫線程之前就已經執行完,因此沒有進入競爭讀鎖階段;而讀線程Thread-1因為在啟動寫線程之后才執行完,這個時候檢查到數據發生變化,因此進入讀鎖階段,保證讀取的數據是最新的。

和ReadWriteLock相比,StampedLock寫入數據的加鎖過程基本類似,不同的是讀取數據。

讀取數據大致的過程如下:

1.嘗試通過tryOptimisticRead()方法樂觀讀模式讀取數據,并返回版本號

2.數據讀取完成后,再通過lock.validate()去驗證版本號,如果在讀取過程中沒有寫入,版本號不會變,驗證成功,直接返回結果

3.如果在讀取過程中有寫入,版本號會發生變化,驗證將失敗。在失敗的時候,再通過悲觀讀鎖再次讀取數據,把讀取的最新結果返回

對于讀多寫少的場景,由于寫入的概率不高,程序在絕大部分情況下可以通過樂觀讀獲取數據,極少數情況下使用悲觀讀鎖獲取數據,并發執行效率得到了大大的提升。

樂觀鎖實際用途也非常廣泛,比如數據庫的字段值修改,我們舉個簡單的例子。

在訂單庫存表上order_store,我們通常會增加了一個數值型版本號字段version,每次更新order_store這個表庫存數據的時候,都將version字段加1,同時檢查version的值是否滿足條件。

select id,... ,version
from order_store
where id = 1000
update order_store
set version = version + 1,...
where id = 1000 and version = 1

數據庫的樂觀鎖,就是查詢的時候將version查出來,更新的時候利用version字段驗證是否一致,如果相等,說明數據沒有被修改,讀取的數據安全;如果不相等,說明數據已經被修改過,讀取的數據不安全,需要重新讀取。

這里的version就類似于StampedLock的stamp值。

2.2、tryConvertToWriteLock 方法

其次,StampedLock還提供了將悲觀讀鎖升級為寫鎖的功能,對應的核心方法是tryConvertToWriteLock()。

它主要使用在if-then-update的場景,即:程序先采用讀模式,如果讀的數據滿足條件,就返回;如果讀的數據不滿足條件,再嘗試寫。

簡單示例如下:

public int readAndWrite(Integer newCount) {
    // 1.獲取讀鎖,也可以使用樂觀讀
    long stamp = lock.readLock();
    int currentValue = count;
    try {
        // 2.檢查是否讀取數據
        while (Objects.isNull(currentValue)) {
            // 3.如果沒有,嘗試升級寫鎖
            long wl = lock.tryConvertToWriteLock(stamp);
            // 4.不為 0 升級寫鎖成功
            if (wl != 0L) {
                // 重新賦值
                stamp = wl;
                count = newCount;
                currentValue = count;
                break;
            } else {
                // 5.升級失敗,釋放之前加的讀鎖并上寫鎖,通過循環再試
                lock.unlockRead(stamp);
                stamp = lock.writeLock();
            }
        }
    } finally {
        // 6.釋放最后加的鎖
        lock.unlock(stamp);
    }
    // 7.返回讀取的數據
    return currentValue;
}

03、小結

總結下來,與ReadWriteLock相比,StampedLock進一步把讀鎖細分為樂觀讀和悲觀讀,能進一步提升了并發執行效率。

好處是非常明顯的,系統性能得到提升,但是代價也不小,主要有以下幾點:

  • 1.代碼邏輯更加復雜,如果編程不當很容易出 bug
  • 2.StampedLock是不可重入鎖,不能在一個線程中反復獲取同一個鎖,如果編程不當,很容易出現死鎖
  • 3.如果線程阻塞在StampedLock的readLock()或者writeLock()方法上時,此時試圖通過interrupt()方法中斷線程,會導致 CPU 飆升。因此,使用 StampedLock一定不要調用中斷操作,如果需要支持中斷功能,推薦使用可中斷的讀鎖readLockInterruptibly()或者寫鎖writeLockInterruptibly()方法。

最后,在實際的使用過程中,樂觀讀編程模型,推薦可以按照以下固定模板編寫。

public int read() {
    // 1.嘗試通過樂觀讀模式讀取數據,非阻塞
    long stamp = lock.tryOptimisticRead();
    // 2.假設x = 0,但是x可能被寫線程修改為1
    int x = count;
    // 3.檢查樂觀讀后是否有其他寫鎖發生
    if(!lock.validate(stamp)){
        // 4.如果有,采用悲觀讀鎖,并重新讀取數據到當前線程局部變量
        stamp = lock.readLock();
        try {
            x = count;
        } finally{
            // 5.釋放悲觀讀鎖
            lock.unlockRead(stamp);
        }
    }
    // 6.返回讀取的數據
    return x;
}

04、參考

1、https://www.liaoxuefeng.com

2、https://zhuanlan.zhihu.com/p/257868603

責任編輯:武曉燕 來源: 潘志的研發筆記
相關推薦

2024-05-15 09:41:22

樂觀鎖編程

2024-04-15 08:32:11

線程讀寫鎖數據庫

2024-06-11 00:01:00

并發validate場景

2021-05-06 16:15:12

Java代碼

2021-03-29 08:54:42

StampedLock線程開發技術

2024-01-29 01:08:01

悲觀鎖遞歸鎖讀寫鎖

2020-12-23 10:10:23

Pythonweb代碼

2022-06-22 09:44:41

Python文件代碼

2022-07-07 10:33:27

Python姿勢代碼

2020-12-09 10:15:34

Pythonweb代碼

2019-11-28 16:00:06

重入鎖讀寫鎖樂觀鎖

2023-03-10 15:45:03

Golang公平鎖

2020-09-16 07:56:28

多線程讀寫鎖悲觀鎖

2021-07-06 08:37:29

Redisson分布式

2023-06-02 08:29:24

https://wwMutex

2021-03-31 10:05:26

偏向鎖輕量級鎖

2023-01-04 13:43:24

讀寫鎖AQS共享模式

2022-02-14 15:07:48

進程FileChanne線程

2021-01-03 09:58:39

StampedLock線程開發技術

2024-10-08 09:10:03

JDK通信并發
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成人免费看片又大又黄 | 黄色一级大片在线免费看产 | 91免费在线视频 | 久久久久亚洲视频 | 啪啪av | 国产精品久久久久久吹潮日韩动画 | 欧美啪啪网站 | 亚洲国产一区在线 | 在线播放国产一区二区三区 | www.99re5.com | 韩国理论电影在线 | 在线a视频网站 | 精品国产一区二区三区成人影院 | 日本三级全黄三级三级三级口周 | 91文字幕巨乱亚洲香蕉 | 韩国精品在线观看 | 欧美一区二区三区在线观看 | 欧美日韩网站 | 中文字幕一区二区三区日韩精品 | 日韩精品久久久久 | 国产精品夜夜春夜夜爽久久电影 | 福利视频日韩 | 午夜免费观看网站 | 欧美电影一区 | 国产亚洲精品美女久久久久久久久久 | 狠狠躁18三区二区一区 | 日韩一级二级片 | 精品二区 | 成人精品国产一区二区4080 | 成人小视频在线观看 | 欧美美女被c | 韩国精品在线观看 | 亚洲精品国产成人 | 成人深夜福利 | 亚洲二区视频 | 成人夜晚看av | 午夜影院中文字幕 | 精品美女久久久 | 欧美精品一区在线发布 | 国产高清一区 | 国产一区二区三区久久久久久久久 |