返回

深入剖析ReentrantReadWriteLock和StampedLock的奥秘:并发访问的多面性

后端

读写锁的艺术:ReentrantReadWriteLock 与 StampedLock

在多线程编程的世界中,协调对共享资源的访问至关重要。读写锁提供了优雅的解决方案,允许多个线程同时读取共享数据,而只允许一个线程写入。在这篇文章中,我们将深入探讨 Java 中两种流行的读写锁:ReentrantReadWriteLock 和 StampedLock。

ReentrantReadWriteLock:分而治之

ReentrantReadWriteLock 顾名思义,是一种可重入的读写锁。它由两个独立的锁组成:读锁和写锁。读锁可以被多个线程同时获取,而写锁只能由一个线程独占。

想象一下一个图书馆的场景。多个读者可以同时阅读一本书(读锁),但只有一个管理员可以同时更新或借出这本书(写锁)。

代码示例:

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

public class Library {

    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private final HashMap<String, Book> books = new HashMap<>();

    public Book getBook(String title) {
        lock.readLock().lock();
        try {
            return books.get(title);
        } finally {
            lock.readLock().unlock();
        }
    }

    public void addBook(Book book) {
        lock.writeLock().lock();
        try {
            books.put(book.getTitle(), book);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

StampedLock:更灵活的读写

StampedLock 是一种较新的读写锁,在 Java 8 中引入。它提供了一种更灵活的读写分离机制,以及对乐观锁的支持。

StampedLock 使用一个称为“邮票”的机制。每个线程在访问共享数据之前需要获取一个邮票。邮票包含两个值:读邮票和写邮票。

与 ReentrantReadWriteLock 类似,读邮票允许线程读取数据,而写邮票允许线程写入数据。然而,StampedLock 支持乐观锁。这意味着线程可以在不持有锁的情况下读取数据。

想象一下一个在线购物网站。多个用户可以同时浏览产品(读邮票),但只有一个用户可以同时下订单(写邮票)。

代码示例:

import java.util.concurrent.locks.StampedLock;
import java.util.HashMap;

public class ShoppingCart {

    private final StampedLock lock = new StampedLock();
    private final HashMap<String, Item> items = new HashMap<>();

    public Item getItem(String name) {
        long stamp = lock.tryOptimisticRead();
        Item item = items.get(name);
        if (!lock.validate(stamp)) {
            // 数据被修改了,重新获取邮票
            stamp = lock.readLock();
            try {
                item = items.get(name);
            } finally {
                lock.unlockRead(stamp);
            }
        }
        return item;
    }

    public void addItem(Item item) {
        long stamp = lock.writeLock();
        try {
            items.put(item.getName(), item);
        } finally {
            lock.unlockWrite(stamp);
        }
    }
}

选择合适的锁

ReentrantReadWriteLock 和 StampedLock 都是有用的读写锁,但它们适合不同的场景。

  • ReentrantReadWriteLock: 适合读写比例较高的情况,并且并发性水平较低。
  • StampedLock: 适合读写比例较低的情况,并且并发性水平较高,或者需要支持乐观锁。

常见问题解答

1. 为什么使用读写锁?

  • 读写锁允许并发读取共享数据,同时防止写冲突。

2. ReentrantReadWriteLock 和 StampedLock 之间的关键区别是什么?

  • ReentrantReadWriteLock 使用独立的读锁和写锁,而 StampedLock 使用邮票机制,并支持乐观锁。

3. 乐观锁是如何工作的?

  • 乐观锁允许线程在不持有锁的情况下读取数据。如果数据在读取期间被修改,则会重新获取锁并再次读取。

4. 什么时候应该使用乐观锁?

  • 乐观锁适合并发读写比率较高的场景,因为可以避免不必要的锁争用。

5. 读写锁有哪些常见应用?

  • 缓存系统、数据库系统和文件系统等。