深入剖析ReentrantReadWriteLock和StampedLock的奥秘:并发访问的多面性
2024-01-17 15:06:42
读写锁的艺术: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. 读写锁有哪些常见应用?
- 缓存系统、数据库系统和文件系统等。