Java中乐观锁和悲观锁

一、锁的底层实现原理

1. 悲观锁的实现细节
  • synchronized 关键字

    • 偏向锁:当锁对象第一次被线程获取时,线程在对象头中记录偏向线程 ID,后续该线程再次获取锁无需 CAS 操作。
    • 轻量级锁:当有其他线程尝试竞争偏向锁时,偏向锁升级为轻量级锁。线程在栈帧中创建锁记录(Lock Record),通过 CAS 将对象头的 Mark Word 复制到锁记录中。
    • 重量级锁:若多个线程交替执行同步块,轻量级锁效率高;但若存在锁竞争,轻量级锁会膨胀为重量级锁,依赖操作系统的互斥量(Mutex),导致线程阻塞和用户态 / 内核态切换。
  • ReentrantLock

    • 基于 AQS(AbstractQueuedSynchronizer)实现,通过 CAS 设置 state 变量表示锁的获取状态。
    • 公平锁:线程按请求顺序获取锁,通过 FIFO 队列实现。
    • 非公平锁:允许线程在队列中插队,减少线程切换开销,但可能导致某些线程饥饿。
2. 乐观锁的实现细节
  • CAS 的硬件支持

    • CAS 操作依赖处理器的cmpxchg指令(x86 架构),确保原子性。
    • Java 中的Unsafe类通过 JNI 调用底层 CAS 指令。
  • 原子类的分层设计

    // AtomicInteger的incrementAndGet()方法源码
    public final int incrementAndGet() {
        return U.getAndAddInt(this, VALUE, 1) + 1;
    }
    
    // Unsafe类的getAndAddInt方法
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
        return v;
    }
    
     
    • weakCompareAndSetIntcompareAndSetInt的区别:前者允许虚假失败(spurious failure),但在 JDK 实现中二者通常等价。

二、锁的性能对比与调优

1. 性能基准测试
  • 测试工具:JMH(Java Microbenchmark Harness)
  • 测试场景
    • 高并发写:悲观锁(ReentrantLock)吞吐量约为 10 万 TPS,乐观锁(AtomicInteger)约为 50 万 TPS。
    • 低冲突写:悲观锁因线程阻塞导致上下文切换开销,乐观锁优势明显。
    • 高冲突写:乐观锁 CAS 重试频繁,吞吐量下降至约 20 万 TPS,此时悲观锁更稳定。
2. 锁的优化策略
  • 锁粗化(Lock Coarsening)

    // 优化前
    public void method() {
        synchronized(this) {
            // 操作1
        }
        synchronized(this) {
            // 操作2
        }
    }
    
    // 优化后(JVM自动优化)
    public void method() {
        synchronized(this) {
            // 操作1
            // 操作2
        }
    }
    
  • 锁消除(Lock Elimination)

    public void method() {
        StringBuffer sb = new StringBuffer();
        sb.append("a"); // StringBuffer的append是synchronized方法,但sb是局部变量,JVM会消除锁
    }
    
  • 分段锁(Striped Lock)

    // ConcurrentHashMap的分段锁设计(JDK7及以前)
    static final int DEFAULT_CONCURRENCY_LEVEL = 16;
    Segment<K,V>[] segments;
    

三、高级应用场景

1. 乐观锁在分布式系统中的应用
  • 数据库乐观锁

    UPDATE table 
    SET column = value, version = version + 1 
    WHERE id = ? AND version = ?;
    
  • 分布式锁

    // 使用Redis实现分布式乐观锁
    public boolean updateWithOptimisticLock(String key, String value, String expectedVersion) {
        String script = 
            "if redis.call('get', KEYS[1]) == ARGV[1] " +
            "then redis.call('set', KEYS[1], ARGV[2]); return 1 " +
            "else return 0 end";
        return redisTemplate.execute(script, Collections.singletonList(key), expectedVersion, value) == 1;
    }
    
2. 悲观锁的扩展应用
  • 可重入锁的嵌套调用
    public class ReentrantExample {
        private final ReentrantLock lock = new ReentrantLock();
        
        public void outer() {
            lock.lock();
            try {
                System.out.println("Outer locked");
                inner();
            } finally {
                lock.unlock();
            }
        }
        
        public void inner() {
            lock.lock(); // 可重入,无需等待
            try {
                System.out.println("Inner locked");
            } finally {
                lock.unlock();
            }
        }
    }
    

四、CAS 的深层问题与解决方案

1. ABA 问题的进阶解决方案
  • AtomicMarkableReference
    private AtomicMarkableReference<Integer> balance = 
        new AtomicMarkableReference<>(100, false);
        
    public void transfer() {
        boolean[] markHolder = new boolean[1];
        int oldBalance = balance.get(markHolder);
        boolean oldMark = markHolder[0];
        // 操作...
        balance.compareAndSet(oldBalance, newBalance, oldMark, !oldMark);
    }
    
2. 原子类的局限性
  • LongAdder vs AtomicLong
    // JDK8引入的LongAdder,在高并发下性能优于AtomicLong
    private LongAdder counter = new LongAdder();
    
    public void increment() {
        counter.increment(); // 分段累加,减少CAS冲突
    }
    
    public long sum() {
        return counter.sum(); // 汇总各分段结果
    }
    

五、锁的选择决策树

是否需要跨进程/节点的锁?
├─ 是 → 使用分布式锁(Redis、ZooKeeper)
└─ 否 → 是否保证强一致性?
   ├─ 是 → 冲突概率高?
   │  ├─ 是 → 使用悲观锁(synchronized/ReentrantLock)
   │  └─ 否 → 使用乐观锁(Atomic类 + 重试)
   └─ 否 → 使用无锁数据结构(ConcurrentHashMap)

六、JDK 9 + 的新特性

1. VarHandle
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;

public class VarHandleExample {
    private static final VarHandle VALUE;
    private int value;
    
    static {
        try {
            VALUE = MethodHandles.lookup().findVarHandle(
                VarHandleExample.class, "value", int.class);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new Error(e);
        }
    }
    
    public void increment() {
        VALUE.getAndAdd(this, 1); // 等价于AtomicInteger的操作
    }
}
2. 增强的 StampedLock
public class Point {
    private double x, y;
    private final StampedLock sl = new StampedLock();
    
    public void move(double deltaX, double deltaY) {
        long stamp = sl.writeLock(); // 获取写锁
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            sl.unlockWrite(stamp);
        }
    }
    
    public double distanceFromOrigin() {
        long stamp = sl.tryOptimisticRead(); // 乐观读
        double currentX = x, currentY = y;
        if (!sl.validate(stamp)) { // 验证期间是否有写操作
            stamp = sl.readLock(); // 升级为悲观读
            try {
                currentX = x;
                currentY = y;
            } finally {
                sl.unlockRead(stamp);
            }
        }
        return Math.hypot(currentX, currentY);
    }
}

总结

选择锁的核心原则:

  1. 读多写少:优先使用乐观锁,配合Atomic类或StampedLock
  2. 写多读少:优先使用悲观锁,配合ReentrantLocksynchronized
  3. 高并发场景:考虑分段锁(如ConcurrentHashMap)或LongAdder
  4. 分布式系统:使用数据库版本号或 Redis 实现乐观锁,或基于 ZooKeeper 实现分布式悲观锁。

现代 JDK 中,锁机制不断演进,从偏向锁到VarHandle,性能和灵活性持续提升。开发者需根据具体场景选择合适的锁策略,并关注 JDK 新特性以优化并发性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值