关于乐观锁

 记一个面试问题,什么是乐观锁和悲观锁?有什么区别?乐观锁怎么实现?

一、什么是乐观锁和悲观锁?

乐观锁:对于获取资源总是持乐观态度,认为操作资源时其他人不会同时操作该资源。在此思想中资源总是可达的,因此不会对资源加锁,只在更新数据时检查数据是否冲突。乐观态度下,可能存在多个线程同时操作同一资源的情况,因而需要一些其他方法来保证数据一致性。

悲观锁:对于获取资源总是持悲观态度,认为操作资源时总是存在冲突的可能。在此思想中资源总是存在冲突,需要加锁保证数据操作,在操作完成后释放锁。

二、乐观锁的实现

乐观锁的实现主要有两种:版本号机制CAS

1.版本号机制:

数据库表设计时添加版本号列,举个例子,张三有一张银行卡,余额为156246.15,数据库表中记录如下:

card_numberownerphone_numberidentity_numberbalanceversion
622xxxxxxxx1234张三 18912341234110000202404225961156246.152462

 张三业余时间在大学后街摆了个烤肠摊赚点外快。此时有两个人A和B分别购买了一根3块钱烤肠,并通过银联向张三付款。A、B的付款操作均需要更新张三的余额,因为乐观锁未对数据操作加锁,所以A、B的款项可能同时到达数据库。引入版本号机制后,假设A的付款操作更新张三的余额时获取到版本号为2462,在A的付款操作完成提交前,B的付款操作也获取到张三此时的余额版本号为2462;A完成数据操作后提交前检查发现版本号还是2462就直接提交了更新,并将版本号升级为2463.

card_numberownerphone_numberidentity_numberbalanceversion
622xxxxxxxx1234张三 18912341234110000202404225961156249.152463

在B完成 数据操作后提交前检查发现版本号变成了2463,与自己此前获取的版本号2462不一致,因此更新失败。此时可以通过重试机制进行重试,或者返回失败信息,由用户重新提交更新。

2.CAS机制:

CAS(Compare And Swap)比较并交换,过程中包含三个数据,内存位置,预期原值和新值。当内存位置的值与预期原址一致时执行更新操作并返回成功,若不一致则不做处理返回失败。

java在JDK1.5后才可以使用CAS操作,该操作由sun.misc.Unsafe类里面的compareAndSwapInt()和compareAndSwapLong()等几个方法包装提供,虚拟机在内部对这些方法做了特殊处理,即时编译出来的结果就是一条平台相关的处理器CAS指令,没有方法调用的过程,或者可以认为是无条件内联进去了。

unsafe类的设计并不是供用户调用的类,如果不采用反射手段,我们只能通过其他的Java API来间接使用它,如java.util.concurrent包里面的整数原子类。

下面是AtomicInteger类中的compareAndSet方法:

   /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

可以看到,compareAndSet实际使用了unsafe类的compareAndSwapInt()方法。

三、乐观锁的缺点

1.冲突处理复杂。乐观锁在提交时需要检查数据是否被其他事务修改,如果发现冲突,需要回滚事务或重新尝试操作,这增加了冲突处理的复杂性

2.高并发场景,并发冲突较多的情况下,即多个事务同时对同一数据进行修改的概率较高时,可能会导致效率降低,因为每个事务在更新数据时都需要检查数据是否被其他事务修改。对于较长事务,频繁的重试或回滚会增加开销。

3.需要额外字段。为了实现乐观锁,通常需要在数据表中添加额外的版本号或时间戳字段,这增加了存储空间开销

4.业务逻辑变复杂,乐观锁是在应用程序层面实现的,如果业务逻辑复杂,可能会导致实现起来较为繁琐。

5.ABA问题,使用CAS实现的乐观锁会存在ABA问题,即线程1操作时将数据由A修改为B,又由B修改为A,线程2去操作时,拿到的数据是A,更新数据前校验时数据仍然是A,数据成功更新。 很多情况下ABA问题没有什么实际影响,但是部分场景ABA会造成错误操作,如有一个栈,栈中依次压入了B和A两个元素。

有两个线程A、B:                                                                                                                             A线程的操作是,如果栈顶元素是A则清空栈,并将D、C、A依次压入。                                           B线程的操作是,如果栈顶元素是A则获取下一个元素B。

线程1获取到栈顶元素是A,于是将A,B依次弹出并依次压入D、C、A,此时线程2获取到栈顶元素也是A,而后线程2尝试获取下一个元素B时,取出的元素却是C。

                     

四、CAS实现乐观锁ABA问题的优化

java.util.concurrent.atomic中提供了AtomicMarkableReference<V>和AtomicStampedReference<V>来解决ABA问题,本质上还是版本号机制。将值与mark或stamp绑定为一个pair,并添加volatile修饰保证线程间的可见性。如下图:

CAS操作修改数据时同时判断reference值和mark(stamp)是否为预期值,符合预期则更新pair,如下为AtomicMarkableReference<V>的compareAndSet方法

    /**
     * Atomically sets the value of both the reference and mark
     * to the given update values if the
     * current reference is {@code ==} to the expected reference
     * and the current mark is equal to the expected mark.
     *
     * @param expectedReference the expected value of the reference
     * @param newReference the new value for the reference
     * @param expectedMark the expected value of the mark
     * @param newMark the new value for the mark
     * @return {@code true} if successful
     */
    public boolean compareAndSet(V       expectedReference,
                                 V       newReference,
                                 boolean expectedMark,
                                 boolean newMark) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedMark == current.mark &&
            ((newReference == current.reference &&
              newMark == current.mark) ||
             casPair(current, Pair.of(newReference, newMark)));
    }
 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JebWoo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值