quarkus依赖注入之九:bean读写锁

打印 上一主题 下一主题

主题 913|帖子 913|积分 2739

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览


  • 本篇是《quarkus依赖注入》的第九篇,目标是在轻松的气氛中学习一个小技能:bean锁
  • quarkus的bean锁本身很简单:用两个注解修饰bean和方法即可,但涉及到多线程同步问题,欣宸愿意花更多篇幅与各位Java程序员一起畅谈多线程,聊个痛快,本篇由以下内容组成

  • 关于多线程同步问题
  • 代码复现多线程同步问题
  • quarkus的bean读写锁
关于读写锁


  • java的并发包中有读写锁ReadWriteLock:在多线程场景中,如果某个对象处于改变状态,可以用写锁加锁,这样所有做读操作对象的线程,在获取读锁时就会block住,直到写锁释放
  • 为了演示bean锁的效果,咱们先来看一个经典的多线程同步问题,如下图,余额100,充值10块,扣费5块,正常情况下最终余额应该是105,但如果充值和扣费是在两个线程同时进行,而且各算各的,再分别用自己的计算结果去覆盖余额,最终会导致计算不准确
代码复现多线程同步问题


  • 咱们用代码来复现上图中的问题,AccountBalanceService是个账号服务类,其成员变量accountBalance表示余额,另外有三个方法,功能分别是:

  • get:返回余额,相当于查询余额服务
  • deposit:充值,入参是充值金额,方法内将余额放入临时变量,然后等待100毫秒模拟耗时操作,再将临时变量与入参的和写入成员变量accountBalance
  • deduct:扣费,入参是扣费金额,方法内将余额放入临时变量,然后等待100毫秒模拟耗时操作,再将临时变量与入参的差写入成员变量accountBalance


  • AccountBalanceService.java源码如下,deposit和deduct这两个方法各算各的,丝毫没有考虑当时其他线程对accountBalance的影响
  1. package com.bolingcavalry.service.impl;
  2. import io.quarkus.logging.Log;
  3. import javax.enterprise.context.ApplicationScoped;
  4. @ApplicationScoped
  5. public class AccountBalanceService {
  6.     // 账户余额,假设初始值为100
  7.     int accountBalance = 100;
  8.     /**
  9.      * 查询余额
  10.      * @return
  11.      */
  12.     public int get() {
  13.         // 模拟耗时的操作
  14.         try {
  15.             Thread.sleep(80);
  16.         } catch (InterruptedException e) {
  17.             e.printStackTrace();
  18.         }
  19.         return accountBalance;
  20.     }
  21.     /**
  22.      * 模拟了一次充值操作,
  23.      * 将账号余额读取到本地变量,
  24.      * 经过一秒钟的计算后,将计算结果写入账号余额,
  25.      * 这一秒内,如果账号余额发生了变化,就会被此方法的本地变量覆盖,
  26.      * 因此,多线程的时候,如果其他线程修改了余额,那么这里就会覆盖掉,导致多线程同步问题,
  27.      * AccountBalanceService类使用了Lock注解后,执行此方法时,其他线程执行AccountBalanceService的方法时就会block住,避免了多线程同步问题
  28.      * @param value
  29.      * @throws InterruptedException
  30.      */
  31.     public void deposit(int value) {
  32.         // 先将accountBalance的值存入tempValue变量
  33.         int tempValue  = accountBalance;
  34.         Log.infov("start deposit, balance [{0}], deposit value [{1}]", tempValue, value);
  35.         // 模拟耗时的操作
  36.         try {
  37.             Thread.sleep(100);
  38.         } catch (InterruptedException e) {
  39.             e.printStackTrace();
  40.         }
  41.         tempValue += value;
  42.         // 用tempValue的值覆盖accountBalance,
  43.         // 这个tempValue的值是基于100毫秒前的accountBalance计算出来的,
  44.         // 如果这100毫秒期间其他线程修改了accountBalance,就会导致accountBalance不准确的问题
  45.         // 例如最初有100块,这里存了10块,所以余额变成了110,
  46.         // 但是这期间如果另一线程取了5块,那余额应该是100-5+10=105,但是这里并没有靠拢100-5,而是很暴力的将110写入到accountBalance
  47.         accountBalance = tempValue;
  48.         Log.infov("end deposit, balance [{0}]", tempValue);
  49.     }
  50.     /**
  51.      * 模拟了一次扣费操作,
  52.      * 将账号余额读取到本地变量,
  53.      * 经过一秒钟的计算后,将计算结果写入账号余额,
  54.      * 这一秒内,如果账号余额发生了变化,就会被此方法的本地变量覆盖,
  55.      * 因此,多线程的时候,如果其他线程修改了余额,那么这里就会覆盖掉,导致多线程同步问题,
  56.      * AccountBalanceService类使用了Lock注解后,执行此方法时,其他线程执行AccountBalanceService的方法时就会block住,避免了多线程同步问题
  57.      * @param value
  58.      * @throws InterruptedException
  59.      */
  60.     public void deduct(int value) {
  61.         // 先将accountBalance的值存入tempValue变量
  62.         int tempValue  = accountBalance;
  63.         Log.infov("start deduct, balance [{0}], deposit value [{1}]", tempValue, value);
  64.         // 模拟耗时的操作
  65.         try {
  66.             Thread.sleep(100);
  67.         } catch (InterruptedException e) {
  68.             e.printStackTrace();
  69.         }
  70.         tempValue -= value;
  71.         // 用tempValue的值覆盖accountBalance,
  72.         // 这个tempValue的值是基于100毫秒前的accountBalance计算出来的,
  73.         // 如果这100毫秒期间其他线程修改了accountBalance,就会导致accountBalance不准确的问题
  74.         // 例如最初有100块,这里存了10块,所以余额变成了110,
  75.         // 但是这期间如果另一线程取了5块,那余额应该是100-5+10=105,但是这里并没有靠拢100-5,而是很暴力的将110写入到accountBalance
  76.         accountBalance = tempValue;
  77.         Log.infov("end deduct, balance [{0}]", tempValue);
  78.     }
  79. }
复制代码

  • 接下来是单元测试类LockTest.java,有几处需要注意的地方稍后会说明
[code]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;@QuarkusTestpublic class LockTest {    @Inject    AccountBalanceService account;    @Test    public void test() throws InterruptedException {        CountDownLatch latch = new CountDownLatch(3);        int initValue = account.get();        final int COUNT = 10;        // 这是个只负责读取的线程,循环读10次,每读一次就等待50毫秒        new Thread(() -> {            for (int i=0;i {            for (int i=0;i {            for (int i=0;i

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

傲渊山岳

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表