Java中的乐观锁和悲观锁

在多线程编程中,锁机制是确保数据一致性和线程安全的关键技术。悲观锁和乐观锁是两种常见的锁机制,它们在不同的场景下有着各自的优势和适用范围。

悲观锁和乐观锁的概念

悲观锁(Pessimistic Locking)假设在并发环境中会发生冲突,因此在访问共享资源时总是先加锁,确保在事务期间没有其他线程可以修改该资源。悲观锁在事务开始时就获取锁,直到事务结束时才释放锁。

乐观锁
乐观锁(Optimistic Locking)假设在并发环境中很少发生冲突,因此在访问共享资源时不立即加锁,而是等到真正需要修改资源时再检查是否有冲突。如果发现冲突,则采取补偿措施(如重试或回滚)。

1. 乐观锁 (Optimistic Locking)

概念

乐观锁假设并发冲突很少发生,因此在操作数据时不会先加锁,而是通过某种机制(如版本号)来检测是否存在冲突。只有在提交时发现冲突才会进行重试。

实现原理

  • 版本号机制 每条记录附加一个版本号字段,每次更新时检查和更新版本号。

    1. 读取数据时,获取当前版本号。

    2. 更新数据时,检查数据库中当前版本号是否与读取时一致。

    3. 如果一致,则更新数据并将版本号加 1;如果不一致,则说明有冲突,操作失败或重试。

  • CAS (Compare and Swap) Java 的 java.util.concurrent 包中大量使用了 CAS 操作,例如 AtomicIntegerAtomicLong

    1. CAS 通过比较内存中的值和预期值,如果一致则更新,否则重试。

优缺点

  • 优点

    • 不需要实际加锁,因此开销小,性能较高。

    • 适用于读多写少的场景。

  • 缺点

    • 在高并发下,频繁重试可能影响性能。

    • 不适合写多读少的场景。

代码示例

版本号机制

// 模拟版本号机制
class Data {
    private int value;
    private int version;
​
    public synchronized boolean updateValue(int newValue, int expectedVersion) {
        if (this.version == expectedVersion) {
            this.value = newValue;
            this.version++;
            return true;
        }
        return false;
    }
}

 

CAS 操作(CAS 通过比较内存中的值和预期值,如果一致则更新,否则重试。

import java.util.concurrent.atomic.AtomicInteger;
​
public class OptimisticLockExample {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(0);
​
        boolean success = atomicInteger.compareAndSet(0, 1);
        System.out.println("Operation success: " + success + ", New value: " + atomicInteger.get());
    }
}

  


2. 悲观锁 (Pessimistic Locking)

概念

悲观锁假设并发冲突会频繁发生,因此在操作数据时,会先加锁来阻止其他线程对数据的访问。只有当前操作完成后,其他线程才能访问。

实现原理

  • 排他锁

    1. 在操作数据前,先获取锁(独占访问)。

    2. 其他线程尝试访问时,会被阻塞直到锁被释放。

  • 数据库层面 使用 SELECT ... FOR UPDATE 的方式锁定数据行。

  • Java 中的锁机制 悲观锁通常使用 synchronizedjava.util.concurrent.locks.Lock 来实现。

优缺点

  • 优点

    • 能有效避免数据冲突。

    • 对于写多读少的场景更适合。

  • 缺点

    • 开销较大(需要维护锁)。

    • 在高并发场景下可能导致线程阻塞,影响性能。

代码示例

使用 synchronized

class Counter {
    private int count = 0;
​
    public synchronized void increment() {
        count++;
    }
​
    public synchronized int getCount() {
        return count;
    }
}

 

使用 Lock

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
class Counter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();
​
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
​
    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

  


对比总结

特性乐观锁悲观锁
并发控制方式 无锁(基于版本号或 CAS) 加锁(阻塞其他线程)
性能 高(冲突少时) 低(锁管理开销大)
适用场景 读多写少 写多读少
冲突处理 依赖重试机制 阻止冲突发生
实现方式 版本号、CAS 操作 synchronized、Lock、数据库排他锁

根据具体的业务场景选择合适的锁机制。

 
posted @ 2024-11-20 10:41  luorx  阅读(3)  评论(0编辑  收藏  举报