锁是数据库的核心之一,不管在工作还是面试中,我们都会经常跟它们打交道。下面小编就为你介绍一下数据库锁之一的乐观锁是什么以及该如何实现吧。
乐观锁是什么?
乐观锁一般相对悲观锁而言的,乐观锁下会假设数据一般情况下不会造成冲突,所以数据已经在进行提交更新的时候,乐观锁才会正式对数据的冲突与否进行检测,如果发现冲突,就会返回给用户错误的信息,让用户自己决定如何去做。
乐观锁采取了更加宽松的加锁机制。因为它是相对悲观锁而言的,这也是为了避免可能的数据库幻读、业务处理时间过长等等原因而引起数据处理错误的一种机制,但乐观锁一般不会去刻意使用数据库本身的锁机制,它会依赖数据本身来保证数据的正确性。
因为是相对于悲观锁的,所以在进行数据库处理处理的时候,乐观锁一般不会使用数据库锁机制。一般实现乐观锁的方式就是记录数据版本。
乐观锁它相信事务之间的数据竞争的概率是比较小的,因此它会尽可能的直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。
如何实现乐观锁?
乐观锁的实现方式有不少,一般而言都会使用CAS来实现乐观锁,CAS是一个项乐观锁实现技术,当多个线程同时尝试使用CAS更新一个变量时,只有一个线程能够成功更新变量值,其他都会失败,失败的线程不会被挂起,而是被告知在这次竞争中失败,并且可以再次尝试更新。
CAS 实现包括了三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。如果没有的话,处理器就不做任何操作。不管是哪种情况,它都会返回在 CAS 指令执行之前该位置的值。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”这与乐观锁的冲突检查+数据更新的原理其实是一样的。
注:乐观锁是一种思想,CAS是实现这种思想一种方式而已。
Java对CAS的支持
在JDK1.5 中新增java.util.concurrent(J.U.C)就是建立在CAS之上的。相对于对于synchronized这种阻塞算法,CAS是非阻塞算法的一种常见实现。所以J.U.C在性能上有了很大的提升。
我们以java.util.concurrent中的AtomicInteger为例,看一下在不使用锁的情况下是如何保证线程安全的。主要理解getAndIncrement方法,该方法的作用相当于 ++i 操作。
在没有锁的机制下需要字段value要借助volatile原语,保证线程间的数据是可见的。这样在获取变量的值的时候才能直接读取。然后来看看++i是怎么做到的。
getAndIncrement采用了CAS技术,它会每次从内存中读取一些数据再将将这些数据与+1后的结果进行CAS操作,成功的话就返回结果,否则就一直试到成功为止。而compareAndSet利用JNI来完成CPU指令的操作。
ABA问题
CAS会导致“ABA问题”。
CAS技术实现的重要前提是需要取出内存中某时刻的数据,而在下时刻比较并替换的话,就会在这个时间差里导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
部分乐观锁的实现是通过版本号(version)的方式来解决ABA问题,乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现ABA问题,因为版本号只会增加不会减少。
总结:
java中的线程安全问题十分重要,想保证线程安全,就需要锁。锁又有两种:乐观锁与悲观锁。悲观锁是独占锁,阻塞锁。乐观锁是非独占锁,非阻塞锁。
以上就是今天的全部内容了,想知道更多java常见问答知识的话请多多关注奇Q工具网了解详情吧。
推荐阅读: