Java乐观锁的实现原理和典型案例

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黑板报】

本文由博客一文多发平台 OpenWrite 发布!

Java中的悲观锁和乐观锁是两种不同的并发控制机制。 1. 悲观锁(Pessimistic Locking): 悲观锁假设并发访问会导致冲突,因此在访问共享资源之前会先获取锁,防止其他线程访问。常见的悲观锁实现方式是使用synchronized关键字或ReentrantLock类。 使用synchronized关键字: ```java synchronized (lockObject) { // 访问共享资源的代码 } ``` 使用ReentrantLock类: ```java Lock lock = new ReentrantLock(); lock.lock(); try { // 访问共享资源的代码 } finally { lock.unlock(); } ``` 2. 乐观锁(Optimistic Locking): 乐观锁假设并发访问不会导致冲突,因此不会加锁,而是在更新共享资源时检查是否有其他线程修改过。如果有其他线程修改过,则需要处理冲突。常见的乐观锁实现方式是使用版本号或时间戳来标识数据的版本。 使用版本号实现乐观锁: ```java class Data { private int value; private int version; public void updateValue(int newValue) { while (true) { int currentVersion = version; if (compareAndSet(currentVersion, newValue)) { // 更新成功 break; } } } private boolean compareAndSet(int expectedVersion, int newValue) { // 检查当前版本是否与期望版本相同 if (version == expectedVersion) { // 更新值和版本 value = newValue; version++; return true; } return false; } } ``` 乐观锁的实现通常使用CAS操作(compare-and-swap),即比较当前值与期望值是否相等,如果相等则更新,否则表示有其他线程修改过。 总的来说,悲观锁在访问共享资源前先获取锁,适用于并发冲突较多的场景;而乐观锁不加锁,通过检查版本或时间戳来处理冲突,适用于并发冲突较少的场景。具体使用哪种锁取决于具体的业务需求和并发情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值