导读:
最近发现某个项目的用户流水和账款金额出现了并发问题, 然后使用乐观锁解决了这个问题, 但是因为有跑批任务 在同一时刻 同一用户的账款 会增加多条流水于是就出现:
- StaleObjectStateException,
- ObjectOptimisticLockingFailureException,
- CannotAcquireLockException
的异常, 虽然 账款的问题是解决了 (单笔没问题后面的批处理还是会把失败的流水继续推过来) 但还是不是一个比较好且简单的解, 开动脑筋,在不改动主要业务逻辑的情况下如何对其打补丁呢?
最简单的方式就是将并行改为串行 然后就在数据库查询修改的方法外面加了synchronized 关键字, 但加上之后并行查询的数据还是老数据 synchronized 居然失效了, 不应该啊, 然后想到了应该是事务未提交导致的,但是方法上有事务注解啊, 接着想到了 事务是通过动态代理实现的显然动态代理的方法并没有synchronized关键字修饰.
这是一个例子:
@Service
class WalletApplicationSerive{
@Transactional(rollbackFor = Exception.class)
public synchronized void pay(accountId, amount, outerCode){
// 数据库查询之后修改
}
}
当我们使用事务的时候 其背后的实现是动态代理结合IOC容器获取出来的WalletApplicationSerive 的实例已经被Spring 换成了(spring 实现的更复杂, 为了方便理解这里以静态代理为例 ,这只是一个简单的示例)
class WalletApplicationSeriveProxy{
private WalletApplicationSerivce tagert;
public void pay(accountId, amount, outerCode){
tx.begin()
try{
tagert.pay(accountId, amount, outerCode)
}catch(Exception e){
tx.rollback()
throw e;
}
tx.commit()
}
}
动态代理:
// 目标对象
Object target ;
Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), Main.class, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 带有@Transcational注解的方法都会被拦截
tx.begin();
try{
method.invoke(target, args);
}catch(Exception e){
tx.rollback();
throw e;
}
tx.commit();
return null;
}
});
一切都变得简单了 这也就是synchronized与ReentrantLock在spring 事务中失效了的原因.
要如何解决呢? 很简单 在 代理类外面增加上事务.
@Service
class WalletApplicationSerive{
@Autowired
InnerWalletApplicationSerive inner;
public synchronized void pay(accountId, amount, outerCode){
inner.pay(accountId, amount, outerCode)
}
@Service
static class InnerWalletApplicationSerive{
@Transactional(rollbackFor = Exception.class)
public void pay(accountId, amount, outerCode){
// 数据库查询之后修改
}
}
}
问题解决, 但这里锁的粒度太粗了, 可以在对锁进行更细的粒度改造:
@Service
class WalletApplicationSerive{
@Autowired
InnerWalletApplicationSerive inner;
public void pay(accountId, amount, outerCode){
synchronized(WalletLockManger.getLock(accountId)){
inner.pay(accountId, amount, outerCode)
}
}
@Service
static class InnerWalletApplicationSerive{
@Transactional(rollbackFor = Exception.class)
public void pay(accountId, amount, outerCode){
// 数据库查询之后修改
}
}
}
class WalletLockManger {
private static final Map<String, String> lockMap = new ConcurrentHashMap<>();
public static String getLock(String accountId) {
return lockMap.computeIfAbsent(accountId, Function.identity());
}
}
synchronized(WalletLockManger.getLock(accountId)) 这个里有很大的改造空间, 后面如果 要部署多个实例的时候 可以将这里换成redis的锁.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。