ReentrantLock 与 ReentrantReadWriteLock 的区别与用法

时间:2025-03-27 14:50:33

ReentrantLock 与 ReentrantReadWriteLock 的区别与用法

1. ReentrantLock

ReentrantLock 是一种可重入互斥锁,它提供了与 synchronized 相同的基本行为和语义,但功能更加强大。其特点包括:

  • 可响应性:锁可以由未持有锁的线程释放,这减少了锁不必要的保持时间。
  • 可中断性:一个正在等待锁的线程可以被中断。
  • 公平性:锁可以设置为公平锁或非公平锁。
  • 条件变量ReentrantLock 配合 Condition 接口提供了比 Objectwait()notify()notifyAll() 方法更强大的等待/通知机制。
示例代码
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private final ReentrantLock lock = new ReentrantLock();
    private int count;

    public void add(int n) {
        lock.lock();
        try {
            count += n;
        } finally {
            lock.unlock();
        }
    }

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

    public static void main(String[] args) {
        Counter counter = new Counter();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> counter.add(1)).start();
        }
    }
}

2. ReentrantReadWriteLock

ReentrantReadWriteLock 是一种读写锁,允许多个读线程同时访问,但只允许一个写线程访问,或者阻塞所有的读写线程。这种锁的设计可以提高性能,特别是在数据结构中,读操作的数量远远超过写操作的情况下。

示例代码
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Counter {
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();
    private int count;

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

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

    public static void main(String[] args) {
        Counter counter = new Counter();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> counter.inc()).start();
        }
        for (int i = 0; i < 10; i++) {
            new Thread(() -> System.out.println(counter.getCount())).start();
        }
    }
}

3. 区别

  • 锁的类型
    • ReentrantLock 是一个互斥锁,同一时间只有一个线程可以持有锁。
    • ReentrantReadWriteLock 是一个读写锁,允许多个读线程同时访问,但写线程独占访问。
  • 性能
    • ReentrantLock 适用于读写操作频繁且写操作较多的场景。
    • ReentrantReadWriteLock 适用于读操作远多于写操作的场景,可以显著提高并发性能。
  • 功能
    • ReentrantLock 提供了更多的功能,如可中断的锁定、尝试锁定、条件变量等。
    • ReentrantReadWriteLock 提供了读锁和写锁,支持锁降级等高级功能。

案例分析

假设有一个共享的数据结构,读操作远多于写操作。使用 ReentrantReadWriteLock 可以显著提高并发性能,因为多个读线程可以同时访问数据,而写线程则独占访问。以下是一个具体的例子:

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CachedData {
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private Object data;
    private boolean cacheValid;

    public void processCachedData() {
        rwl.readLock().lock();
        try {
            if (!cacheValid) {
                rwl.readLock().unlock();
                rwl.writeLock().lock();
                try {
                    if (!cacheValid) {
                        data = fetchDataFromDatabase();
                        cacheValid = true;
                    }
                    rwl.readLock().lock();
                } finally {
                    rwl.writeLock().unlock();
                }
            }
            use(data);
        } finally {
            rwl.readLock().unlock();
        }
    }

    private Object fetchDataFromDatabase() {
        // 模拟从数据库获取数据
        return new Object();
    }

    private void use(Object data) {
        // 模拟使用数据
        System.out.println("使用数据: " + data);
    }

    public static void main(String[] args) {
        CachedData cachedData = new CachedData();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> cachedData.processCachedData()).start();
        }
    }
}

在这个例子中,processCachedData 方法首先尝试获取读锁。如果缓存无效,它会释放读锁并获取写锁来更新缓存。更新完成后,它会进行写锁到读锁的降级,允许其他线程并发读取。

通过这种方式,ReentrantReadWriteLock 可以在确保数据一致性的同时,提高多线程环境下的性能。