『Java』面试官:如何实现一个乐观锁(小白都能看得懂的代码)( 二 )


本文插图
这里我们可以看到自增操作主要是使用了unsafe的getAndAddInt方法 。 因为不是专门介绍AtomicInteger , 所以不会对源码进行相信的分析 。
(1)Unsafe:Unsafe是位于sun.misc包下的一个类 , Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力 。 也就是说我们直接操作了内存空间进行了加1操作 。
(2) unsafe.getAndAddInt:其内部又调用了Unsafe.compareAndSwapInt方法 。 这个机制叫做CAS机制 ,
CAS 即比较并替换 , 实现并发算法时常用到的一种技术 。 CAS操作包含三个操作数——内存位置、预期原值及新值 。 执行CAS操作的时候 , 将内存位置的值与预期原值比较 , 如果相匹配 , 那么处理器会自动将该位置值更新为新值 , 否则 , 处理器不做任何操作 。
我们使用一个例子来解释相信你会更加的清楚 。
比如说给你儿子订婚 。 你儿子就是内存位置 , 你原本以为你儿子是和杨贵妃在一起了 , 结果在订婚的时候发现儿子身边是西施 。 这时候该怎么办呢?你一气之下不做任何操作 。 如果儿子身边是你预想的杨贵妃 , 你一看很开心就给他们订婚了 , 也叫作执行操作 。 现在你应该明白了吧 。
但是这样的CAS机制会带来一个比较常见的问题 。 那就是ABA问题 , 举个例子 , 你看到桌子上有100块钱 , 然后你去干其他事了 , 回来之后看到桌子上依然是100块钱 , 你就认为这100块没人动过 , 其实在你走的那段时间 , 别人已经拿走了100块 , 后来又还回来了 。 这就是ABA问题 。
『Java』面试官:如何实现一个乐观锁(小白都能看得懂的代码)
本文插图
那这时候又该如何解决ABA问题呢?既然有人动了 , 那我们对数据加一个版本控制字段 , 只要有人动过这个数据 , 就把版本进行增加 , 我们看到桌子上有100块钱版本是1 , 回来后发现桌子上100没变 , 但是版本却是2 , 就立马明白100块有人动过 。
5、乐观锁思想
OK , 上面说了这么多 , 其实就是想说一句话那就是乐观锁可以由CAS机制+版本机制来实现 。
乐观锁假设认为数据一般情况下不会产生并发冲突 , 所以在数据进行提交更新的时候 , 才会正式对数据是否产生并发冲突进行检测 , 如果发现并发冲突了 , 则让返回用户错误的信息 , 让用户决定如何去做 。
(1)CAS机制:当多个线程尝试使用CAS同时更新同一个变量时 , 只有其中一个线程能更新变量的值 , 而其它线程都失败 。 CAS 有效地说明了“ 我认为位置 V 应该包含值 A;如果包含该值 , 则将 B 放到这个位置;否则 , 不要更改该位置 , 只告诉我这个位置现在的值即可“ 。
(2)版本机制:CAS机制保证了在更新数据的时候没有被修改为其他数据的同步机制 , 版本机制就保证了没有被修改过的同步机制(意思是上面的ABA问题) 。
基于这个思想我们就可以实现一个乐观锁 。 下面我们写一下代码 。 这个代码在我自己电脑上亲测通过 。
二、实现一个乐观锁第一步:定义我们要操作的数据
『Java』面试官:如何实现一个乐观锁(小白都能看得懂的代码)
本文插图
第二步:定义一个乐观锁
『Java』面试官:如何实现一个乐观锁(小白都能看得懂的代码)
本文插图
第三步:测试
『Java』面试官:如何实现一个乐观锁(小白都能看得懂的代码)
本文插图
定义了两个线程 , 然后进行读写操作
第四步:输出结果
『Java』面试官:如何实现一个乐观锁(小白都能看得懂的代码)
本文插图
这个结果可以看到在读数据的时候只要发现没有变化即可 , 但是更新数据的时候要判断当前的版本号和预期的版本号是否一致 , 如果一致那就更新 , 如果不一致 , 那就说明更新失败 。