1. 概述

简单来说,Lock 是一种比标准 synchronized 块更灵活、更强大的线程同步机制。

Lock 接口自 Java 1.5 起就存在了。它定义在 java.util.concurrent.lock 包中,提供了丰富的锁操作能力。

本教程将探讨 Lock 接口的不同实现及其应用场景。

2. Lock 与 synchronized 块的差异

使用 synchronized 块和 Lock API 有几个关键区别:

  • 作用范围限制
    synchronized 块必须完全包含在方法内。而 Lock API 的 lock()unlock() 操作可以分散在不同方法中。

  • 公平性支持
    synchronized 块不支持公平性。锁释放后任何线程都可能获取,无法指定优先级。Lock API 可通过 fairness 属性实现公平性,确保等待时间最长的线程优先获取锁。

  • 非阻塞获取
    线程无法获取 synchronized 块时会被阻塞。Lock API 提供 tryLock() 方法,线程仅在锁可用时获取,避免不必要的阻塞。

  • 可中断等待
    等待 synchronized 块的线程无法被中断。Lock API 提供 lockInterruptibly() 方法,允许中断等待锁的线程。

3. Lock 接口方法

Lock 接口的核心方法包括:

  • void lock()
    获取锁(若可用)。若锁不可用,线程将阻塞直到锁被释放。

  • void lockInterruptibly()
    类似 lock(),但允许阻塞线程被中断并抛出 InterruptedException

  • boolean tryLock()
    lock() 的非阻塞版本。尝试立即获取锁,成功返回 true。

  • boolean tryLock(long timeout, TimeUnit timeUnit)
    增强版 tryLock(),等待指定超时时间后放弃获取锁。

  • void unlock()
    释放锁实例。

踩坑提醒:锁定的实例必须显式解锁,否则会导致死锁。推荐使用 try-finally 结构:

Lock lock = ...;
lock.lock();
try {
    // 访问受保护资源
} finally {
    lock.unlock();
}

除 Lock 接口外,还有 ReadWriteLock 接口,它维护一对锁:

  • 读锁(支持多线程并发读)
  • 写锁(独占式写操作)

ReadWriteLock 的关键方法:

  • Lock readLock() - 返回读操作锁
  • Lock writeLock() - 返回写操作锁

4. 锁实现

4.1 ReentrantLock

ReentrantLock 实现了 Lock 接口,提供与 synchronized 相同的并发和内存语义,但功能更强大。

基本用法示例:

public class ReentrantLockExample {
    private final Lock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}

tryLock() 超时机制:

public class TryLockExample {
    private final Lock lock = new ReentrantLock();

    public void tryLockExample() throws InterruptedException {
        boolean isLocked = lock.tryLock(1, TimeUnit.SECONDS);
        if (isLocked) {
            try {
                // 访问受保护资源
            } finally {
                lock.unlock();
            }
        } else {
            // 执行备用逻辑
        }
    }
}

调用 tryLock(1, SECONDS) 的线程会等待 1 秒,超时后自动放弃。

4.2 ReentrantReadWriteLock

ReentrantReadWriteLock 实现 ReadWriteLock 接口,遵循以下规则:

  • 读锁获取
    当没有线程持有写锁或请求写锁时,多个线程可获取读锁。

  • 写锁获取
    仅当没有线程在读或写时,单个线程可获取写锁。

完整示例:

public class ReadWriteLockExample {
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();

    // 写操作(独占)
    public void writeOperation() {
        writeLock.lock();
        try {
            // 临界区写操作
        } finally {
            writeLock.unlock();
        }
    }

    // 读操作(共享)
    public void readOperation() {
        readLock.lock();
        try {
            // 临界区读操作
        } finally {
            readLock.unlock();
        }
    }
}

4.3 StampedLock

Java 8 引入的 StampedLock 支持读写锁,但采用 stamp(票据)机制管理锁状态:

public class StampedLockExample {
    private final StampedLock stampedLock = new StampedLock();

    public void writeOperation() {
        long stamp = stampedLock.writeLock();
        try {
            // 写操作
        } finally {
            stampedLock.unlockWrite(stamp);
        }
    }

    public void readOperation() {
        long stamp = stampedLock.readLock();
        try {
            // 读操作
        } finally {
            stampedLock.unlockRead(stamp);
        }
    }
}

乐观锁特性:读操作通常无需等待写操作完成,可通过乐观读升级:

public void optimisticRead() {
    long stamp = stampedLock.tryOptimisticRead();
    // 非阻塞读取数据
    if (!stampedLock.validate(stamp)) {
        // 数据被修改,升级为读锁
        stamp = stampedLock.readLock();
        try {
            // 重新读取数据
        } finally {
            stampedLock.unlockRead(stamp);
        }
    }
    // 使用读取的数据
}

5. 使用 Condition

Condition 类允许线程在临界区内等待特定条件。当线程获取锁但缺少执行条件时(如队列空时读线程等待),传统方法使用 wait()/notify(),而 Condition 支持多条件控制:

public class ConditionExample {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    public void awaitCondition() throws InterruptedException {
        lock.lock();
        try {
            condition.await(); // 等待条件
        } finally {
            lock.unlock();
        }
    }

    public void signalCondition() {
        lock.lock();
        try {
            condition.signal(); // 通知等待线程
        } finally {
            lock.unlock();
        }
    }
}

6. 总结

本文探讨了 Lock 接口的不同实现(包括 Java 8 的 StampedLock)以及 Condition 类在多条件控制中的应用。相比 synchronized,Lock API 提供了更精细的锁控制能力,是构建高性能并发系统的利器。

文中完整示例代码可在 GitHub 上 找到。


原始标题:Guide to java.util.concurrent.Locks | Baeldung