美文网首页一些收藏
@Transactional 事务加了 锁 为什么还有并发问题?

@Transactional 事务加了 锁 为什么还有并发问题?

作者: 瞎胡扯1 | 来源:发表于2022-10-13 15:33 被阅读0次

一、原因分析

Spring 中通过在方法上添加注解 @Transactional 可以很好的处理事务问题。Spring对此的处理原理是对 加了 @Transactional 注解的方法 添加 AOP切面来时先事务管理的。
synchronized 最大范围也就是方法级别的。 事务和synchronized 关系如下所示

图片

由上图可以看出,当线程1 释放了锁,还未提交事务之前,线程2 已经获取锁并提前提交了事务,从而导致了并发的问题。

二、解决方法

1、方法一 增强事务隔离级别

可以把事务的隔离级别设置为 SERIALIZABLE 不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读,但是效率最低。

 @Transactional(isolation = Isolation.SERIALIZABLE)
    public synchronized void update(Long id, Long seq){
        Test1Entity entity = test1Mapper.selectById(id);
        test1Mapper.updateById(new Test1Entity(id, seq + entity.getSeq()));
    }

2、方法二 提升加锁位置

如果业务逻辑非常简单,对高并发行要求不高的话,可以把 锁操作添加到 控制层,如下所示:

 // Controller
   @PutMapping("/update/{id}/{seq}")
    public Test1Entity update(@PathVariable("id") Long id,
                             @PathVariable("seq") Long seq){
        // 添加锁操作。 
        synchronized (Class.class){ // 此处为了示例,要根据业务合理加锁
            testService.update(id, seq);
        }
        return new Test1Entity(id, seq);
    }

 // Service
@Service
public class TestServiceImpl {
    @Resource
    private Test1Mapper test1Mapper;
    
    @Transactional
    public void update(Long id, Long seq){
        Test1Entity entity = test1Mapper.selectById(id);
        test1Mapper.updateById(new Test1Entity(id, seq + entity.getSeq()));
    }
}

3、方法三 抽离事务代码

方法一和方法二的效率都比较低,另一种方式可以把,可以把需要 把需要并发控制的业务,单独抽离出来,进行事务控制操作。如下所示:


public void complex(Long id, Long seq){
        // 其他业务处理
        lock.lock();
        try {
            service.update(id, seq);
        } finally {
           lock.unlock();
        }
        // 其他业务处理
    }

    @Transactional
    public void update(Long id, Long seq){
        Test1Entity entity = test1Mapper.selectById(id);
        test1Mapper.updateById(new Test1Entity(id, seq + entity.getSeq()));
    }

4、方法四 手动开启事务

方法三中可能会增加一个类的编写,也可以在同一个方法中通过手动开启事务的方式实现。如下所示:


@Service
public class TestServiceImpl {

    @Resource
    private Test1Mapper test1Mapper;

    private static final Lock lock = new ReentrantLock();
    
    @Resource
    private  DataSourceTransactionManager transactionManager;
    @Resource
    TransactionDefinition transactionDefinition;

    public void update(Long id, Long seq){
        // 其他业务处理
        lock.lock();
        // 开启事务
        TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);;
        try {
            Test1Entity entity = test1Mapper.selectById(id);
            test1Mapper.updateById(new Test1Entity(id, seq + entity.getSeq()));

            // 提交事务
            transactionManager.commit(transaction);
        }catch (Exception e){
            // 回滚事务
            transactionManager.rollback(transaction);
        }finally {
           lock.unlock();
        }
        // 其他业务处理
    }

}

这个手动开启事务,需要每个方法都需要实现,这个也是比较繁琐,这种方式可以抽象出一个公共类,统一来实现事务的处理。 可以自己脑补

相关文章

网友评论

    本文标题:@Transactional 事务加了 锁 为什么还有并发问题?

    本文链接:https://www.haomeiwen.com/subject/xcufzrtx.html