这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
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;
}
/**
* 模拟了一次充值操作,
* 将账号余额读取到本地变量,
* 经过一秒钟的计算后,将计算结果写入账号余额,
* 这一秒内,如果账号余额发生了变化,就会被此方法的本地变量覆盖,
* 因此,多线程的时候,如果其他线程修改了余额,那么这里就会覆盖掉,导致多线程同步问题,
* AccountBalanceService类使用了Lock注解后,执行此方法时,其他线程执行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);
}
/**
* 模拟了一次扣费操作,
* 将账号余额读取到本地变量,
* 经过一秒钟的计算后,将计算结果写入账号余额,
* 这一秒内,如果账号余额发生了变化,就会被此方法的本地变量覆盖,
* 因此,多线程的时候,如果其他线程修改了余额,那么这里就会覆盖掉,导致多线程同步问题,
* AccountBalanceService类使用了Lock注解后,执行此方法时,其他线程执行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;
// 这是个只负责读取的线程,循环读10次,每读一次就等待50毫秒
new Thread(() -> {
for (int i=0;i<COUNT;i++) {
// 读取账号余额
Log.infov("current balance {0}", account.get());
}
latch.countDown();
}).start();
// 这是个充值的线程,循环充10次,每次存2元
new Thread(() -> {
for (int i=0;i<COUNT;i++) {
account.deposit(2);
}
latch.countDown();
}).start();
// 这是个扣费的线程,循环扣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);
}
}
上述代码中,有以下几点需要注意
quarkus为bean提供了读写锁方案:Lock注解,借助它,可以为bean的所有方法添加同一把写锁,再手动将读锁添加到指定的读方法,这样在多线程操作的场景下,也能保证数据的正确性
来看看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
名称
链接
备注
项目主页
https://github.com/zq2599/blog_demos
该项目在GitHub上的主页
git仓库地址(https)
https://github.com/zq2599/blog_demos.git
该项目源码的仓库地址,https协议
git仓库地址(ssh)
git@github.com:zq2599/blog_demos.git
该项目源码的仓库地址,ssh协议
这个git项目中有多个文件夹,本次实战的源码在quarkus-tutorials文件夹下,如下图红框
quarkus-tutorials是个父工程,里面有多个module,本篇实战的module是basic-di,如下图红框
手机扫一扫
移动阅读更方便
你可能感兴趣的文章