Java乐观锁的实现原理
什么是乐观锁?
在并发编程中,多个线程同时对同一资源进行操作时,需要使用锁来保证数据的一致性。
乐观锁与悲观锁是两种不同的锁机制。
悲观锁会在整个操作期间占用资源的独占性,以保证数据的一致性,而乐观锁则是基于版本号或时间戳的机制,在操作前做一个乐观的估计,如果操作成功,则版本号加1,如果失败,则重试。因为乐观锁不需要在整个操作期间占用资源的独占性,所以可以提高并发性。
Java中乐观锁的实现方式
Java中的乐观锁主要有两种实现方式:CAS(Compare and Swap)和版本号控制。
CAS
CAS是实现乐观锁的核心算法,它通过比较内存中的值是否和预期的值相等来判断是否存在冲突。如果存在,则返回失败;如果不存在,则执行更新操作。
CAS 它包含了 3 个参数:V(需要更新的变量)、E(预期值)和 N(最新值)。只有当需要更新的变量等于预期值时,需要更新的变量才会被设置为最新值,如果更新值和预期值不同,则说明已经有其它线程更新了需要更新的变量,此时当前线程不做操作,返回 V 的真实值。
Java中提供了AtomicInteger、AtomicLong、AtomicReference等原子类来支持CAS操作。
下面是一个简单的CAS示例:
代码
public class Counter {
private AtomicInteger value = new AtomicInteger(0);
public void increment() {
int expect;
int update;
do {
expect = value.get();
update = expect + 1;
} while (!value.compareAndSet(expect, update));
}
}
在这个示例中,increment()方法会不断地执行CAS操作,直到更新成功为止。
版本号控制
版本号控制是乐观锁的另一种实现方式。
每当一个线程要修改数据时,都会先读取当前的版本号或时间戳,并将其保存下来。线程完成修改后,会再次读取当前的版本号或时间戳,如果发现已经变化,则说明有其他线程对数据进行了修改,此时需要回滚并重试。
下面是一个简单的版本号控制示例:
代码
public class Counter {
private int value = 0;
private int version = 0;
public void increment() {
int currentVersion;
int currentValue;
do {
currentVersion = version;
currentValue = value;
currentValue++;
} while (currentVersion != version);
value = currentValue;
version = currentVersion + 1;
}
}
在这个示例中,increment()方法会不断地执行循环,直到当前的版本号与保存的版本号相同为止。如果版本号不同,则说明有其他线程修改了数据,此时需要回滚并重试。
Java中乐观锁的案例
ConcurrentHashMap
ConcurrentHashMap是Java中的一个线程安全的哈希表实现,它使用分离锁(Segment)来保证线程安全。每个Segment都是一个独立的哈希表,每个操作只锁定相关的Segment,因此可以支持更高的并发性。
ConcurrentHashMap使用了一种基于CAS的技术来实现乐观锁,它通过比较当前的value和预期的value是否相等来判断是否存在冲突。如果存在,则返回失败;如果不存在,则执行更新操作。
LongAdder
在 JDK1.8 中,Java 提供了一个新的原子类 LongAdder。LongAdder 在高并发场景下会比 AtomicInteger 和 AtomicLong 的性能更好,代价就是会消耗更多的内存空间。
LongAdder 的原理就是降低操作共享变量的并发数,也就是将对单一共享变量的操作压力分散到多个变量值上,将竞争的每个写线程的 value 值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的 value 值进行 CAS 操作,最后在读取值的时候会将原子操作的共享变量与各个分散在数组的 value 值相加,返回一个近似准确的数值。
数据库并发控制
在日常开发中,使用乐观锁最常见的场景就是数据库的更新操作了。
为了保证操作数据库的原子性,我们常常会为每一条数据定义一个版本号,并在更新前获取到它,到了更新数据库的时候,还要判断下已经获取的版本号是否被更新过,如果没有,则执行该操作。
总结
在Java中,并发编程的时候可以使用Synchronized 和 Lock 实现的同步锁机制,但是两种同步锁都属于悲观锁。
悲观锁在高并发的场景下,激烈的锁竞争会造成线程阻塞,大量阻塞线程会导致系统的上下文切换,增加系统的性能开销,这个时候乐观锁就是我们可以考虑的一种方案。
当然CAS 乐观锁在平常使用时比较受限,它只能保证单个变量操作的原子性,当涉及到多个变量时,CAS 就无能为力了。
好了,以上就是今天分享的全部内容,enjoy~ 欢迎关注我的微信公众号【AI黑板报】文章来源:https://www.toymoban.com/news/detail-528159.html
本文由博客一文多发平台 OpenWrite 发布!文章来源地址https://www.toymoban.com/news/detail-528159.html
到了这里,关于Java乐观锁的实现原理和典型案例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!