独占写并发读访问
有时需要进程同时写入和读取相同的数据。
ReadWriteLock
接口及其 ReentrantReadWriteLock
实现允许访问模式,可以描述如下:
- 可以有任意数量的并发数据读取器。如果授予至少一个读者访问权限,则无法访问作者。
- 数据最多只能有一个编写器。如果授予了写入者访问权限,则没有读者可以访问该数据。
实现可能如下所示:
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Sample {
// Our lock. The constructor allows a "fairness" setting, which guarantees the chronology of lock attributions.
protected static final ReadWriteLock RW_LOCK = new ReentrantReadWriteLock();
// This is a typical data that needs to be protected for concurrent access
protected static int data = 0;
/** This will write to the data, in an exclusive access */
public static void writeToData() {
RW_LOCK.writeLock().lock();
try {
data++;
} finally {
RW_LOCK.writeLock().unlock();
}
}
public static int readData() {
RW_LOCK.readLock().lock();
try {
return data;
} finally {
RW_LOCK.readLock().unlock();
}
}
}
注 1 :这个精确的用例使用 AtomicInteger
有一个更清晰的解决方案,但这里描述的是一种访问模式,无论这里的数据是作为 Atomic 变体的整数,这都是有效的。
注 2 :读取部件上的锁是非常需要的,尽管对于不经意的读者来说可能看起来不那么明显。实际上,如果你没有锁定阅读器方面,任何数量的东西都可能出错,其中包括:
- 原始值的写入并不保证在所有 JVM 上都是原子的,因此如果
data
是 64 位长类型,读者可以看到例如 64 位写入中只有 32 位 - 只有当我们在写入和读取之间建立 Happen Before 关系时,JVM 才能保证来自未执行它的线程的写入的可见性。当读者和作者都使用各自的锁时,就建立了这种关系,但不是这样
Version >= Java SE 8
如果需要更高的性能,在某些类型的使用下,有一种更快的锁定类型,称为 StampedLock
,其中包括实现乐观锁定模式。此锁定与 ReadWriteLock
的工作方式非常不同,此样本不能转置。