乐观锁与悲观锁原理

一、乐观锁

 总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。

 version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

核心SQL代码:

update table set x=x+1, version=version+1 where id=#{id} and version=#{version};  

 

 CAS操作方式:即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。

二、悲观锁

 总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized的思想也是悲观锁。

/**
 * 乐观锁
 * 
 * 场景:有一个对象value,需要被两个线程调用,由于是共享数据,存在脏数据的问题
 * 悲观锁可以利用synchronized实现,这里不提.
 * 现在用乐观锁来解决这个脏数据问题
 * 
 * @author lxz
 *
 */
public class OptimisticLock {
    public static int value = 0; // 多线程同时调用的操作对象

    /**
     * A线程要执行的方法
     */
    public static void invoke(int Avalue, String i)
            throws InterruptedException {
        Thread.sleep(1000L);//延长执行时间
        if (Avalue != value) {//判断value版本
            System.out.println(Avalue + ":" + value + "A版本不一致,不执行");
            value--;
        } else {
            Avalue++;//对数据操作
            value = Avalue;;//对数据操作
            System.out.println(i + ":" + value);
        }
    }

    /**
     * B线程要执行的方法
     */
    public static void invoke2(int Bvalue, String i)
            throws InterruptedException {
        Thread.sleep(1000L);//延长执行时间
        if (Bvalue != value) {//判断value版本
            System.out.println(Bvalue + ":" + value + "B版本不一致,不执行");
        } else {
            System.out.println("B:利用value运算,value="+Bvalue);
        }
    }

    /**
     * 测试,期待结果:B线程执行的时候value数据总是当前最新的
     */
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {//A线程
            public void run() {
                try {
                    for (int i = 0; i < 3; i++) {
                        int Avalue = OptimisticLock.value;//A获取的value
                        OptimisticLock.invoke(Avalue, "A");
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {//B线程
            public void run() {
                try {
                    for (int i = 0; i < 3; i++) {
                        int Bvalue = OptimisticLock.value;//B获取的value
                        OptimisticLock.invoke2(Bvalue, "B");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

}

测试结果:

A:1
0:1B版本不一致,不执行
B:利用value运算,value=1
A:2
B:利用value运算,value=2
A:3
 

从结果中看出,B线程在执行的时候最后发现自己的value和执行前不一致,说明被A修改了,那么放弃了本次执行.

 

多运行几次发现了下面的结果:

A:1
B:利用value运算,value=0
A:2
1:2B版本不一致,不执行
A:3
B:利用value运算,value=2


从结果看A修改了value值,B却没有检查出来,利用错误的value值进行了操作. 为什么会这样呢?

这里就回到前面说的乐观锁是有一定的不安全性的,B在检查版本的时候A还没有修改,在B检查完版本后更新数据前(例子中的输出语句),A更改了value值,这时B执行更新数据(例子中的输出语句)就发生了与现存value不一致的现象.

 

针对这个问题,我觉得乐观锁要解决这个问题还需要在检查版本与更新数据这个操作的时候能够使用悲观锁,比如加上synchronized,让它在最后一步保证数据的一致性.这样既保证多线程都能同时执行,牺牲最后一点的性能去保证数据的一致.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值