国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

quarkus依賴注入之九:bean讀寫鎖

這篇具有很好參考價值的文章主要介紹了quarkus依賴注入之九:bean讀寫鎖。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

歡迎訪問我的GitHub

這里分類和匯總了欣宸的全部原創(chuàng)(含配套源碼):https://github.com/zq2599/blog_demos

本篇概覽

  • 本篇是《quarkus依賴注入》的第九篇,目標是在輕松的氣氛中學習一個小技能:bean鎖
  • quarkus的bean鎖本身很簡單:用兩個注解修飾bean和方法即可,但涉及到多線程同步問題,欣宸愿意花更多篇幅與各位Java程序員一起暢談多線程,聊個痛快,本篇由以下內容組成
  1. 關于多線程同步問題
  2. 代碼復現(xiàn)多線程同步問題
  3. quarkus的bean讀寫鎖

關于讀寫鎖

  • java的并發(fā)包中有讀寫鎖ReadWriteLock:在多線程場景中,如果某個對象處于改變狀態(tài),可以用寫鎖加鎖,這樣所有做讀操作對象的線程,在獲取讀鎖時就會block住,直到寫鎖釋放
  • 為了演示bean鎖的效果,咱們先來看一個經(jīng)典的多線程同步問題,如下圖,余額100,充值10塊,扣費5塊,正常情況下最終余額應該是105,但如果充值和扣費是在兩個線程同時進行,而且各算各的,再分別用自己的計算結果去覆蓋余額,最終會導致計算不準確
quarkus依賴注入之九:bean讀寫鎖

代碼復現(xiàn)多線程同步問題

  • 咱們用代碼來復現(xiàn)上圖中的問題,AccountBalanceService是個賬號服務類,其成員變量accountBalance表示余額,另外有三個方法,功能分別是:
  1. get:返回余額,相當于查詢余額服務
  2. deposit:充值,入?yún)⑹浅渲到痤~,方法內將余額放入臨時變量,然后等待100毫秒模擬耗時操作,再將臨時變量與入?yún)⒌暮蛯懭氤蓡T變量accountBalance
  3. deduct:扣費,入?yún)⑹强圪M金額,方法內將余額放入臨時變量,然后等待100毫秒模擬耗時操作,再將臨時變量與入?yún)⒌牟顚懭氤蓡T變量accountBalance
  • AccountBalanceService.java源碼如下,deposit和deduct這兩個方法各算各的,絲毫沒有考慮當時其他線程對accountBalance的影響
package com.bolingcavalry.service.impl;

import io.quarkus.logging.Log;
import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class AccountBalanceService {

    // 賬戶余額,假設初始值為100
    int accountBalance = 100;

    /**
     * 查詢余額
     * @return
     */
    public int get() {
        // 模擬耗時的操作
        try {
            Thread.sleep(80);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return accountBalance;
    }

    /**
     * 模擬了一次充值操作,
     * 將賬號余額讀取到本地變量,
     * 經(jīng)過一秒鐘的計算后,將計算結果寫入賬號余額,
     * 這一秒內,如果賬號余額發(fā)生了變化,就會被此方法的本地變量覆蓋,
     * 因此,多線程的時候,如果其他線程修改了余額,那么這里就會覆蓋掉,導致多線程同步問題,
     * AccountBalanceService類使用了Lock注解后,執(zhí)行此方法時,其他線程執(zhí)行AccountBalanceService的方法時就會block住,避免了多線程同步問題
     * @param value
     * @throws InterruptedException
     */
    public void deposit(int value) {
        // 先將accountBalance的值存入tempValue變量
        int tempValue  = accountBalance;
        Log.infov("start deposit, balance [{0}], deposit value [{1}]", tempValue, value);

        // 模擬耗時的操作
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        tempValue += value;

        // 用tempValue的值覆蓋accountBalance,
        // 這個tempValue的值是基于100毫秒前的accountBalance計算出來的,
        // 如果這100毫秒期間其他線程修改了accountBalance,就會導致accountBalance不準確的問題
        // 例如最初有100塊,這里存了10塊,所以余額變成了110,
        // 但是這期間如果另一線程取了5塊,那余額應該是100-5+10=105,但是這里并沒有靠攏100-5,而是很暴力的將110寫入到accountBalance
        accountBalance = tempValue;

        Log.infov("end deposit, balance [{0}]", tempValue);
    }

    /**
     * 模擬了一次扣費操作,
     * 將賬號余額讀取到本地變量,
     * 經(jīng)過一秒鐘的計算后,將計算結果寫入賬號余額,
     * 這一秒內,如果賬號余額發(fā)生了變化,就會被此方法的本地變量覆蓋,
     * 因此,多線程的時候,如果其他線程修改了余額,那么這里就會覆蓋掉,導致多線程同步問題,
     * AccountBalanceService類使用了Lock注解后,執(zhí)行此方法時,其他線程執(zhí)行AccountBalanceService的方法時就會block住,避免了多線程同步問題
     * @param value
     * @throws InterruptedException
     */
    public void deduct(int value) {
        // 先將accountBalance的值存入tempValue變量
        int tempValue  = accountBalance;
        Log.infov("start deduct, balance [{0}], deposit value [{1}]", tempValue, value);

        // 模擬耗時的操作
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        tempValue -= value;

        // 用tempValue的值覆蓋accountBalance,
        // 這個tempValue的值是基于100毫秒前的accountBalance計算出來的,
        // 如果這100毫秒期間其他線程修改了accountBalance,就會導致accountBalance不準確的問題
        // 例如最初有100塊,這里存了10塊,所以余額變成了110,
        // 但是這期間如果另一線程取了5塊,那余額應該是100-5+10=105,但是這里并沒有靠攏100-5,而是很暴力的將110寫入到accountBalance
        accountBalance = tempValue;

        Log.infov("end deduct, balance [{0}]", tempValue);
    }
}
  • 接下來是單元測試類LockTest.java,有幾處需要注意的地方稍后會說明
package com.bolingcavalry;

import com.bolingcavalry.service.impl.AccountBalanceService;
import io.quarkus.logging.Log;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import javax.inject.Inject;
import java.util.concurrent.CountDownLatch;

@QuarkusTest
public class LockTest {

    @Inject
    AccountBalanceService account;

    @Test
    public void test() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        int initValue = account.get();

        final int COUNT = 10;

        // 這是個只負責讀取的線程,循環(huán)讀10次,每讀一次就等待50毫秒
        new Thread(() -> {

            for (int i=0;i<COUNT;i++) {
                // 讀取賬號余額
                Log.infov("current balance {0}", account.get());
            }

            latch.countDown();
        }).start();

        // 這是個充值的線程,循環(huán)充10次,每次存2元
        new Thread(() -> {
            for (int i=0;i<COUNT;i++) {
                account.deposit(2);
            }
            latch.countDown();
        }).start();

        // 這是個扣費的線程,循環(huán)扣10次,每取1元
        new Thread(() -> {
            for (int i=0;i<COUNT;i++) {
                account.deduct(1);
            }
            latch.countDown();
        }).start();

        latch.await();

        int finalValue = account.get();
        Log.infov("finally, current balance {0}", finalValue);
        Assertions.assertEquals(initValue + COUNT, finalValue);
    }
}
  • 上述代碼中,有以下幾點需要注意
  1. 在主線程中新增了三個子線程,分別執(zhí)行查詢、充值、扣費的操作,可見deposit和deduct方法是并行執(zhí)行的
  2. 初始余額100,充值一共20元,扣費一共10元,因此最終正確結果應該是110元
  3. 為了確保三個子線程全部執(zhí)行完畢后主線程才退出,這里用了CountDownLatch,在執(zhí)行latch.await()的時候主線程就開始等待了,等到三個子線程把各自的latch.await()都執(zhí)行后,主線程才會繼續(xù)執(zhí)行
  4. 最終會檢查余額是否等于110,如果不是則單元測試不通過
  • 執(zhí)行單元測試,結果如下圖,果然失敗了
quarkus依賴注入之九:bean讀寫鎖
  • 來分析測試過程中的日志,有助于我們理解問題的原因,如下圖,充值和扣費同時開始,充值先完成,此時余額是102,但是扣費無視102,依舊使用100作為余額去扣費,然后將扣費結果99寫入余額,導致余額與正確的邏輯產(chǎn)生差距

quarkus依賴注入之九:bean讀寫鎖

  • 反復運行上述單元測試,可以發(fā)現(xiàn)每次得到的結果都不一樣,這算是典型的多線程同步問題了吧...
  • 看到這里,經(jīng)驗豐富的您應該想到了多種解決方式,例如下面這五種都可以:
  1. 用傳統(tǒng)的synchronized關鍵字修飾三個方法
  2. java包的讀寫鎖
  3. deposit和deduct方法內部,不要使用臨時變量tempValue,將余額的類型從int改成AtomicInteger,再使用addAndGet方法計算并設置
  4. 用MySQL的樂觀鎖
  5. 用Redis的分布式鎖
  • 沒錯,上述方法都能解決問題,現(xiàn)在除了這些,quarku還從bean的維度為我們提供了一種新的方法:bean讀寫鎖,接下來細看這個bean讀寫鎖

Container-managed Concurrency:quarkus基于bean的讀寫鎖方案

  • quarkus為bean提供了讀寫鎖方案:Lock注解,借助它,可以為bean的所有方法添加同一把寫鎖,再手動將讀鎖添加到指定的讀方法,這樣在多線程操作的場景下,也能保證數(shù)據(jù)的正確性
  • 來看看Lock注解源碼,很簡單的幾個屬性,要重點注意的是:默認屬性為Type.WRITE,也就是寫鎖,被Lock修飾后,鎖類型有三種選擇:讀鎖,寫鎖,無鎖
@InterceptorBinding
@Inherited
@Target(value = { TYPE, METHOD })
@Retention(value = RUNTIME)
public @interface Lock {

    /**
     * 
     * @return the type of the lock
     */
    @Nonbinding
    Type value() default Type.WRITE;

    /**
     * If it's not possible to acquire the lock in the given time a {@link LockException} is thrown.
     * 
     * @see java.util.concurrent.locks.Lock#tryLock(long, TimeUnit)
     * @return the wait time
     */
    @Nonbinding
    long time() default -1l;

    /**
     * 
     * @return the wait time unit
     */
    @Nonbinding
    TimeUnit unit() default TimeUnit.MILLISECONDS;

    public enum Type {
        /**
         * Acquires the read lock before the business method is invoked.
         */
        READ,
        /**
         * Acquires the write (exclusive) lock before the business method is invoked.
         */
        WRITE,
        /**
         * Acquires no lock.
         * <p>
         * This could be useful if you need to override the behavior defined by a class-level interceptor binding.
         */
        NONE
    }

}
  • 接下來看看如何用bean鎖解AccountBalanceService的多線程同步問題

  • 為bean設置讀寫鎖很簡單,如下圖紅框1,給類添加Lock注解后,AccountBalanceService的每個方法都默認添加了寫鎖,如果想修改某個方法的鎖類型,可以像紅框2那樣指定,Lock.Type.READ表示將get方法改為讀鎖,如果不想給方法上任何鎖,就使用Lock.Type.NONE

quarkus依賴注入之九:bean讀寫鎖
  • 這里預測一下修改后的效果
  1. 在deposit和deduct都沒有被調用時,get方法可以被調用,而且可以多線程同時調用,因為每個線程都能順利拿到讀鎖
  2. 一旦deposit或者deduct被調用,其他線程在調用deposit、deduct、get方法時都被阻塞了,因為此刻不論讀鎖還是寫鎖都拿不到,必須等deposit執(zhí)行完畢,它們才重新去搶鎖
  3. 有了上述邏輯,再也不會出現(xiàn)deposit和deduct同時修改余額的情況了,預測單元測試應該能通過
  4. 這種讀寫鎖的方法雖然可以確保邏輯正確,但是代價不?。ㄒ粋€線程執(zhí)行,其他線程等待),所以在并發(fā)性能要求較高的場景下要慎用,可以考慮樂觀鎖、AtomicInteger這些方式來降低等待代價
  • 再次運行單元測試,如下圖,測試通過
quarkus依賴注入之九:bean讀寫鎖
  • 再來看看測試過程中的日志,如下圖,之前的幾個方法同時執(zhí)行的情況已經(jīng)消失了,每個方法在執(zhí)行的時候,其他線程都在等待

quarkus依賴注入之九:bean讀寫鎖

  • 至此,bean鎖知識點學習完畢,希望本篇能給您一些參考,為您的并發(fā)編程中添加新的方案

源碼下載

  • 本篇實戰(zhàn)的完整源碼可在GitHub下載到,地址和鏈接信息如下表所示(https://github.com/zq2599/blog_demos)
名稱 鏈接 備注
項目主頁 https://github.com/zq2599/blog_demos 該項目在GitHub上的主頁
git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該項目源碼的倉庫地址,https協(xié)議
git倉庫地址(ssh) git@github.com:zq2599/blog_demos.git 該項目源碼的倉庫地址,ssh協(xié)議
  • 這個git項目中有多個文件夾,本次實戰(zhàn)的源碼在quarkus-tutorials文件夾下,如下圖紅框
    quarkus依賴注入之九:bean讀寫鎖
  • quarkus-tutorials是個父工程,里面有多個module,本篇實戰(zhàn)的module是basic-di,如下圖紅框
    quarkus依賴注入之九:bean讀寫鎖

歡迎關注博客園:程序員欣宸

學習路上,你不孤單,欣宸原創(chuàng)一路相伴...文章來源地址http://www.zghlxwxcb.cn/news/detail-632054.html

到了這里,關于quarkus依賴注入之九:bean讀寫鎖的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • quarkus依賴注入之十:學習和改變bean懶加載規(guī)則

    quarkus依賴注入之十:學習和改變bean懶加載規(guī)則

    這里分類和匯總了欣宸的全部原創(chuàng)(含配套源碼):https://github.com/zq2599/blog_demos 本篇是《quarkus依賴注入》系列的第十篇,來看一個容易被忽略的知識點:bean的懶加載,咱們先去了解quarkus框架下的懶加載規(guī)則,然后更重要的是掌握如何改變規(guī)則,以達到提前實例化的目標 總的來

    2024年02月14日
    瀏覽(25)
  • quarkus依賴注入之七:生命周期回調

    quarkus依賴注入之七:生命周期回調

    這里分類和匯總了欣宸的全部原創(chuàng)(含配套源碼):https://github.com/zq2599/blog_demos 本篇的知識點是bean的生命周期回調:在bean生命周期的不同階段,都可以觸發(fā)自定義代碼的執(zhí)行 觸發(fā)自定義代碼執(zhí)行的具體方式,是用對應的注解去修飾要執(zhí)行的方法,如下圖所示: 有兩種模式可以

    2024年02月14日
    瀏覽(23)
  • quarkus依賴注入之六:發(fā)布和消費事件

    quarkus依賴注入之六:發(fā)布和消費事件

    這里分類和匯總了欣宸的全部原創(chuàng)(含配套源碼):https://github.com/zq2599/blog_demos 本文是《quarkus依賴注入》系列的第六篇,主要內容是學習事件的發(fā)布和接收 如果您用過Kafka、RabbitMQ等消息中間件,對消息的作用應該不會陌生,通過消息的訂閱和發(fā)布可以降低系統(tǒng)之間的耦合性,

    2024年02月14日
    瀏覽(18)
  • quarkus依賴注入之八:裝飾器(Decorator)

    quarkus依賴注入之八:裝飾器(Decorator)

    這里分類和匯總了欣宸的全部原創(chuàng)(含配套源碼):https://github.com/zq2599/blog_demos 本篇是《quarkus依賴注入》系列的第八篇,目標是掌握quarkus實現(xiàn)的一個CDI特性:裝飾器(Decorator) 提到裝飾器,熟悉設計模式的讀者應該會想到裝飾器模式,個人覺得下面這幅圖很好的解釋了裝飾器

    2024年02月14日
    瀏覽(45)
  • quarkus依賴注入之五:攔截器(Interceptor)

    quarkus依賴注入之五:攔截器(Interceptor)

    這里分類和匯總了欣宸的全部原創(chuàng)(含配套源碼):https://github.com/zq2599/blog_demos 本文是《quarkus依賴注入》系列的第五篇,經(jīng)過前面的學習,咱們熟悉了依賴注入的基本特性,接下來進一步了解相關的高級特性,先從本篇的攔截器開始 如果您熟悉spring的話,對攔截器應該不會陌

    2024年02月14日
    瀏覽(45)
  • quarkus依賴注入之十二:禁用類級別攔截器

    quarkus依賴注入之十二:禁用類級別攔截器

    這里分類和匯總了欣宸的全部原創(chuàng)(含配套源碼):https://github.com/zq2599/blog_demos 本篇是《quarkus依賴注入》系列的第十二篇,繼續(xù)學習攔截器的另一個高級特性:禁用類級別攔截器 本篇由以下內容構成 編碼驗證類攔截器和方法攔截器的疊加效果 用注解 NoClassInterceptors 使類攔截器

    2024年02月13日
    瀏覽(53)
  • quarkus依賴注入之十三:其他重要知識點大串講(終篇)

    quarkus依賴注入之十三:其他重要知識點大串講(終篇)

    這里分類和匯總了欣宸的全部原創(chuàng)(含配套源碼):https://github.com/zq2599/blog_demos 本篇是《quarkus依賴注入》系列的終篇,前面十二篇已覆蓋quarkus依賴注入的大部分核心內容,但依然漏掉了一些知識點,今天就將剩下的內容匯總,來個一鍋端,輕松愉快的結束這個系列 總的來說,

    2024年02月13日
    瀏覽(25)
  • quarkus依賴注入之十一:攔截器高級特性上篇(屬性設置和重復使用)

    quarkus依賴注入之十一:攔截器高級特性上篇(屬性設置和重復使用)

    這里分類和匯總了欣宸的全部原創(chuàng)(含配套源碼):https://github.com/zq2599/blog_demos 本篇是《quarkus依賴注入》系列的第十一篇,之前的[《攔截器》]學習了攔截器的基礎知識,現(xiàn)在咱們要更加深入的了解攔截器,掌握兩種高級用法:攔截器屬性和重復使用攔截器 先來回顧攔截器的基

    2024年02月13日
    瀏覽(49)
  • Springboot依賴注入Bean的三種方式,final+構造器注入Bean

    @Autowired注解的一大使用場景就是Field Injection。 通過Java的反射機制實現(xiàn),所以private的成員也可以被注入具體的對象 優(yōu)點 代碼少,簡潔明了。 新增依賴十分方便,不需要修改原有代碼 缺點 容易出現(xiàn)空指針異常。Field 注入允許構建對象實例時依賴的對象為空,導致空指針異常

    2024年02月02日
    瀏覽(22)
  • 基于Xml方式Bean的配置-Bean的依賴注入以及·自動裝配

    基于Xml方式Bean的配置-Bean的依賴注入以及·自動裝配

    Bean的依賴注入方式 注入方式 配置方式 通過Bean的set方法注入 通過構造Bean的方法進行注入 其中,ref是reference的縮寫形式,翻譯為:涉及,參考的意思,用于引用其它Bean的id,value用于指定屬性值 注入數(shù)據(jù)類型 普通數(shù)據(jù)類型:String、int、boolean,通過value屬性指定 引用數(shù)據(jù)類型

    2024年02月07日
    瀏覽(27)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包