跳转至

锁机制 (Lock Mechanism)

深入理解ReentrantLock、ReadWriteLock、StampedLock等显式锁的实现原理和使用场景

目录


1. 为什么需要显式锁 (Why Explicit Locks?)

1.1 synchronized的局限性

虽然synchronized关键字简单易用,但在某些场景下存在局限性:

  1. 无法中断 - 获取锁的线程无法被中断
  2. 无法超时 - 无法设置获取锁的超时时间
  3. 非公平锁 - 默认是非公平锁,可能导致线程饥饿
  4. 功能单一 - 只有一种锁模式,无法实现读写分离

1.2 显式锁的优势

**显式锁(Explicit Lock)**提供了更灵活的锁机制:

  • 可中断 - 可以响应中断
  • 可超时 - 可以设置超时时间
  • 公平锁 - 支持公平锁和非公平锁
  • 读写分离 - ReadWriteLock支持读写分离
  • 条件变量 - 支持多个条件变量

1.3 在算力平台中的应用

在算力平台的结算系统中,需要更灵活的锁机制:

/**
 * 算力平台中的锁应用场景
 * Lock Usage in Computing Platform
 */
public class PlatformLock {

    // 场景1:需要超时的锁操作
    private ReentrantLock billingLock = new ReentrantLock();

    public boolean tryDeduct(Long userId, Long amount, long timeout) {
        try {
            // 尝试获取锁,设置超时时间
            if (billingLock.tryLock(timeout, TimeUnit.SECONDS)) {
                try {
                    // 执行扣费操作
                    return deduct(userId, amount);
                } finally {
                    billingLock.unlock();
                }
            }
            return false; // 超时未获取到锁
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }

    // 场景2:读写分离的场景
    private ReadWriteLock configLock = new ReentrantReadWriteLock();

    public String getConfig(String key) {
        configLock.readLock().lock(); // 读锁
        try {
            return configMap.get(key);
        } finally {
            configLock.readLock().unlock();
        }
    }

    public void updateConfig(String key, String value) {
        configLock.writeLock().lock(); // 写锁
        try {
            configMap.put(key, value);
        } finally {
            configLock.writeLock().unlock();
        }
    }
}

2. ReentrantLock (ReentrantLock)

2.1 核心特点

**ReentrantLock(可重入锁)**是java.util.concurrent.locks包中最常用的锁实现。

特性 说明
可重入 同一线程可以多次获取锁
可中断 支持中断获取锁的操作
可超时 支持超时获取锁
公平锁 支持公平锁和非公平锁

2.2 基本使用

import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLock基本使用
 * Basic Usage of ReentrantLock
 */
public class ReentrantLockDemo {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock(); // 获取锁
        try {
            count++;
        } finally {
            lock.unlock(); // 释放锁(必须在finally中)
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

关键点: - lock() - 获取锁,如果锁被占用则阻塞 - unlock() - 释放锁,必须在finally块中调用 - 必须手动释放锁,否则会导致死锁

2.3 可中断锁

/**
 * 可中断锁示例
 * Interruptible Lock Example
 */
public class InterruptibleLockDemo {
    private final ReentrantLock lock = new ReentrantLock();

    public void interruptibleMethod() throws InterruptedException {
        lock.lockInterruptibly(); // 可中断的获取锁
        try {
            // 执行任务
            while (true) {
                // 如果被中断,会抛出InterruptedException
                Thread.sleep(1000);
            }
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        InterruptibleLockDemo demo = new InterruptibleLockDemo();
        Thread thread = new Thread(() -> {
            try {
                demo.interruptibleMethod();
            } catch (InterruptedException e) {
                System.out.println("线程被中断");
            }
        });

        thread.start();
        thread.interrupt(); // 中断线程
    }
}

2.4 超时锁

/**
 * 超时锁示例
 * Timeout Lock Example
 */
public class TimeoutLockDemo {
    private final ReentrantLock lock = new ReentrantLock();

    public boolean tryLockWithTimeout(long timeout, TimeUnit unit) {
        try {
            // 尝试获取锁,设置超时时间
            if (lock.tryLock(timeout, unit)) {
                try {
                    // 执行任务
                    doSomething();
                    return true;
                } finally {
                    lock.unlock();
                }
            }
            return false; // 超时未获取到锁
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
}

2.5 公平锁 vs 非公平锁

/**
 * 公平锁 vs 非公平锁
 * Fair Lock vs Non-Fair Lock
 */
public class FairLockDemo {
    // 公平锁:按照请求锁的顺序分配锁
    private ReentrantLock fairLock = new ReentrantLock(true);

    // 非公平锁:不保证顺序(默认,性能更好)
    private ReentrantLock nonFairLock = new ReentrantLock(false);

    public void fairMethod() {
        fairLock.lock();
        try {
            // 公平锁:保证先请求的线程先获得锁
        } finally {
            fairLock.unlock();
        }
    }

    public void nonFairMethod() {
        nonFairLock.lock();
        try {
            // 非公平锁:可能后请求的线程先获得锁(性能更好)
        } finally {
            nonFairLock.unlock();
        }
    }
}

对比:

特性 公平锁 非公平锁
顺序保证 按请求顺序分配 不保证顺序
性能 较低(需要维护队列) 较高(默认)
适用场景 需要公平性的场景 大多数场景(默认)

2.6 条件变量(Condition)

import java.util.concurrent.locks.Condition;

/**
 * Condition条件变量示例
 * Condition Example
 */
public class ConditionDemo {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private boolean ready = false;

    public void waitForReady() throws InterruptedException {
        lock.lock();
        try {
            while (!ready) {
                condition.await(); // 等待条件满足
            }
            // 条件满足,继续执行
        } finally {
            lock.unlock();
        }
    }

    public void setReady() {
        lock.lock();
        try {
            ready = true;
            condition.signalAll(); // 唤醒所有等待的线程
        } finally {
            lock.unlock();
        }
    }
}

Condition的优势: - 可以创建多个条件变量 - 更灵活的线程间通信 - 类似Object.wait()notify(),但更强大


3. ReadWriteLock (ReadWriteLock)

3.1 核心特点

**ReadWriteLock(读写锁)**将锁分为读锁和写锁,实现读写分离。

特性 说明
读锁 多个线程可以同时持有读锁
写锁 写锁是排他的,与其他锁互斥
读写分离 读多写少场景性能更好
降级 支持锁降级(写锁降级为读锁)

3.2 基本使用

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * ReadWriteLock基本使用
 * Basic Usage of ReadWriteLock
 */
public class ReadWriteLockDemo {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private Map<String, String> cache = new HashMap<>();

    // 读操作:使用读锁
    public String get(String key) {
        lock.readLock().lock();
        try {
            return cache.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }

    // 写操作:使用写锁
    public void put(String key, String value) {
        lock.writeLock().lock();
        try {
            cache.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

3.3 锁的兼容性

读锁 vs 读锁:✅ 兼容(多个线程可以同时持有读锁)
读锁 vs 写锁:❌ 互斥
写锁 vs 写锁:❌ 互斥

3.4 锁降级

/**
 * 锁降级示例
 * Lock Downgrade Example
 */
public class LockDowngradeDemo {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private volatile boolean update = false;

    public void processData() {
        lock.readLock().lock();
        if (!update) {
            lock.readLock().unlock();

            // 升级为写锁
            lock.writeLock().lock();
            try {
                if (!update) {
                    // 更新数据
                    updateData();
                    update = true;
                }
                // 降级为读锁
                lock.readLock().lock();
            } finally {
                lock.writeLock().unlock(); // 释放写锁,但持有读锁
            }
        }

        try {
            // 使用读锁读取数据
            readData();
        } finally {
            lock.readLock().unlock();
        }
    }
}

注意: ReadWriteLock不支持锁升级(读锁升级为写锁),只支持降级。

3.5 在算力平台中的应用

/**
 * 算力平台中的ReadWriteLock应用
 * ReadWriteLock in Computing Platform
 */
public class PlatformReadWriteLock {
    private final ReadWriteLock configLock = new ReentrantReadWriteLock();
    private Map<String, Object> configMap = new ConcurrentHashMap<>();

    // 读配置:频繁操作,使用读锁
    public Object getConfig(String key) {
        configLock.readLock().lock();
        try {
            return configMap.get(key);
        } finally {
            configLock.readLock().unlock();
        }
    }

    // 更新配置:低频操作,使用写锁
    public void updateConfig(String key, Object value) {
        configLock.writeLock().lock();
        try {
            configMap.put(key, value);
            // 通知配置变更
            notifyConfigChange(key, value);
        } finally {
            configLock.writeLock().unlock();
        }
    }

    // 批量读取配置:读锁允许并发
    public Map<String, Object> getAllConfigs() {
        configLock.readLock().lock();
        try {
            return new HashMap<>(configMap); // 返回副本
        } finally {
            configLock.readLock().unlock();
        }
    }
}

4. StampedLock (StampedLock)

4.1 核心特点

**StampedLock(戳记锁)**是JDK 1.8引入的新锁,提供了三种模式:

模式 说明 特点
写锁 排他锁 与其他锁互斥
读锁 共享锁 多个线程可以同时持有
乐观读 无锁读取 性能最好,但需要验证

4.2 基本使用

import java.util.concurrent.locks.StampedLock;

/**
 * StampedLock基本使用
 * Basic Usage of StampedLock
 */
public class StampedLockDemo {
    private final StampedLock lock = new StampedLock();
    private double x, y;

    // 写锁
    public void move(double deltaX, double deltaY) {
        long stamp = lock.writeLock(); // 获取写锁,返回戳记
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            lock.unlockWrite(stamp); // 使用戳记释放锁
        }
    }

    // 读锁
    public double distanceFromOrigin() {
        long stamp = lock.readLock(); // 获取读锁
        try {
            return Math.sqrt(x * x + y * y);
        } finally {
            lock.unlockRead(stamp);
        }
    }

    // 乐观读(性能最好)
    public double optimisticDistanceFromOrigin() {
        long stamp = lock.tryOptimisticRead(); // 尝试乐观读
        double currentX = x, currentY = y;

        if (!lock.validate(stamp)) { // 验证戳记是否有效
            // 戳记无效,升级为读锁
            stamp = lock.readLock();
            try {
                currentX = x;
                currentY = y;
            } finally {
                lock.unlockRead(stamp);
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

4.3 乐观读的优势

**乐观读(Optimistic Read)**是无锁读取,性能最好:

/**
 * 乐观读示例
 * Optimistic Read Example
 */
public class OptimisticReadDemo {
    private final StampedLock lock = new StampedLock();
    private int value = 0;

    public int optimisticRead() {
        long stamp = lock.tryOptimisticRead(); // 无锁读取
        int currentValue = value;

        // 验证期间是否有写操作
        if (lock.validate(stamp)) {
            return currentValue; // 戳记有效,直接返回
        } else {
            // 戳记无效,升级为读锁
            stamp = lock.readLock();
            try {
                return value;
            } finally {
                lock.unlockRead(stamp);
            }
        }
    }

    public void write(int newValue) {
        long stamp = lock.writeLock();
        try {
            value = newValue;
        } finally {
            lock.unlockWrite(stamp);
        }
    }
}

适用场景: 读多写少,且读操作不需要强一致性。

4.4 StampedLock vs ReadWriteLock

特性 ReadWriteLock StampedLock
读锁 支持 支持
写锁 支持 支持
乐观读 不支持 支持(性能最好)
锁降级 支持 不支持
可重入 支持 不支持
性能 较好 更好(乐观读)

5. 锁的性能对比 (Lock Performance Comparison)

5.1 性能测试场景

场景 synchronized ReentrantLock ReadWriteLock StampedLock
高竞争写 中等 较好 较差 最好
高竞争读 中等 中等 最好 最好(乐观读)
读多写少 中等 中等 最好 最好(乐观读)
写多读少 较好 较好 较差 最好

5.2 选择建议

使用synchronized: - 简单的同步场景 - 不需要高级功能(中断、超时等)

使用ReentrantLock: - 需要中断或超时功能 - 需要公平锁 - 需要多个条件变量

使用ReadWriteLock: - 读多写少场景 - 需要锁降级

使用StampedLock: - 读多写少场景 - 可以容忍乐观读的弱一致性 - 追求最高性能


6. 最佳实践 (Best Practices)

6.1 始终在finally中释放锁

// ✅ 正确:在finally中释放锁
lock.lock();
try {
    // 执行任务
} finally {
    lock.unlock();
}

// ❌ 错误:可能忘记释放锁
lock.lock();
// 执行任务
lock.unlock(); // 如果中间抛出异常,锁不会被释放

6.2 避免嵌套锁

// ❌ 不推荐:嵌套锁可能导致死锁
lock1.lock();
try {
    lock2.lock(); // 如果其他线程以相反顺序获取锁,可能死锁
    try {
        // ...
    } finally {
        lock2.unlock();
    }
} finally {
    lock1.unlock();
}

// ✅ 推荐:统一锁顺序
// 或者使用超时锁
if (lock1.tryLock(5, TimeUnit.SECONDS)) {
    try {
        if (lock2.tryLock(5, TimeUnit.SECONDS)) {
            try {
                // ...
            } finally {
                lock2.unlock();
            }
        }
    } finally {
        lock1.unlock();
    }
}

6.3 选择合适的锁类型

// 读多写少:使用ReadWriteLock或StampedLock
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

// 需要超时:使用ReentrantLock
ReentrantLock lock = new ReentrantLock();
if (lock.tryLock(5, TimeUnit.SECONDS)) {
    // ...
}

// 简单场景:使用synchronized
synchronized (this) {
    // ...
}

7. 面试高频问题 (Interview Questions)

Q1: ReentrantLock和synchronized的区别?

答案:

特性 synchronized ReentrantLock
可中断
可超时
公平锁 否(非公平) 是(可选)
条件变量 单一(wait/notify) 多个(Condition)
性能 JVM优化后较好 较好
使用 简单 需要手动释放

Q2: ReadWriteLock的实现原理?

答案: 内部维护读锁计数器和写锁状态,读锁可以共享,写锁排他。

Q3: StampedLock的乐观读是什么?

答案: 无锁读取,通过戳记验证是否有写操作,性能最好但需要验证。

Q4: 如何避免死锁?

答案: 1. 避免嵌套锁 2. 统一锁顺序 3. 使用超时锁 4. 使用锁超时机制

Q5: 锁降级和锁升级的区别?

答案: - 锁降级:写锁降级为读锁(ReadWriteLock支持) - 锁升级:读锁升级为写锁(ReadWriteLock不支持,可能导致死锁)


📖 扩展阅读


返回: 07-Java并发编程
上一章: 07-02 - 线程同步与内存模型
下一章: 07-04 - 并发工具类 →