service层加需要加锁吗_面试官:了解乐观锁和悲观锁吗?

本文探讨了数据库级别的锁是否必要,解释了悲观锁的概念和MySQL中的使用方法,通过示例展示了悲观锁如何防止并发问题。同时,介绍了乐观锁的机制,并给出了在Java中实现乐观锁的示例,最后总结了悲观锁和乐观锁的适用场景和优缺点。
摘要由CSDN通过智能技术生成

faab82e4b30d3ac6cb77b948c54a6c4f.png

为什么会使用到数据库级别的锁?

你可能会有这么一个疑问:现在的程序已经提供了很完善的锁机制来保证多线程的安全问题,还需要用到数据库级别的锁吗?我觉得还是需要的,为什么呢?理由很简单,我们再编程中使用的大部分锁都是单机,尤其是现在分布式集群的流行,这种单机的锁机制就保证不了线程安全了,这个时候,你可能又会想到使用redis的setNX分布式锁或者zookeeper的强一致性来保证线程安全,但是这里我们需要考虑到一个问题,那就是成本问题,有的时候使用redis分布式锁以及zookeeper会增加维护的成本,结合实际出发,再说没有百分百安全的程序,所以再数据库层加锁,也能将安全再提升一级,所以还是有必要的。

什么是悲观锁

悲观锁,正如其名,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

通俗的讲:开启一个事务之后开启悲观锁,这时候数据库将会锁着你需要查询的某条数据或者某张表,其他事务中的查询将会处于阻塞状态,开启悲观锁事务里面操作不会被阻塞,这点有点类似java中的互斥锁(其中的可重入锁),那什么时候锁记录?什么时候锁整张表呢?接着往下看。

mysql悲观锁如何使用?

1.在查询后加:for update

2.需要先开启事务,否者悲观锁无效

3.执行完查询之后一定要接上update语句,否者其他事物会一直处于阻塞状态,直到第一个事务抛出异常为止。

我们看一个例子,假如用户现在有100块钱,买充电器需要100,买耳机也需要100,这时候用户同时买下这两款商品,会发生什么事情呢?

我们分别说一下正常情况和加了悲观锁的情况,这里暂时不讨论程序锁的问题,如果想了解程序中的锁,请参考:java并发编程之synchronized、java并发编程之ReentrantReadWriteLock读写锁等等。

我在数据库新建了一张表:

619758c7e7dab0122537e06f2c632b28.png

表比较简单,我们只需要关注用户id和用户余额,我们等会会用到,我们现在就来模拟一下同时扣款100元,会发生什么情况,直接上代码

单元测试代码:

 @Resource
    private IUserWalletService userWalletService;

@Test
    void deductMoney() throws InterruptedException {
        //需要扣除的金额
        BigDecimal meney = BigDecimal.valueOf(100l);

        //新建第一个线程t1
        Thread t1 = new Thread(() ->{
            //线程1:让用户1扣除100元
            userWalletService.deductMoney(1, meney);
        });
        //新建第一个线程t2
        Thread t2 = new Thread(() ->{
            //线程2:让用户1扣除100元
            userWalletService.deductMoney(1, meney);
        });
        //启动线程1
        t1.start();
        //启动线程1
        t2.start();
        //让线程同步
        t1.join();
        t2.join();
        System.out.println("执行完毕");

    }

service代码:

private UserWalletMapper userWalletMapper;


    UserWalletServiceImpl(UserWalletMapper userWalletMapper){
        this.userWalletMapper = userWalletMapper;
    }

    @Override
    @Transactional
    public void deductMoney(int userId, BigDecimal money) {
        //获取线程名
        String threadName = Thread.currentThread().getName();
        //查询当前用户钱包信息
        UserWallet userWallet = userWalletMapper.getWalletByUserId(userId);
        log.info("线程&
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值