前面在Java并发编程札记-(四)JUC锁-02Lock与ReentrantLock一文中已经学习了ReentrantLock,其中提到了ReentrantLock是互斥锁。与互斥锁相对应的是共享锁。ReadWriteLock就是一种共享锁。ReentrantReadWriteLock是支持与ReentrantLock 类似语义的ReadWriteLock实现。
ReadWriteLock维护了两个锁,读锁和写锁,所以一般称其为读写锁。写锁是独占的。读锁是共享的,如果没有写锁,读锁可以由多个线程共享。与互斥锁相比,虽然一次只能有一个写线程可以修改共享数据,但大量读线程可以同时读取共享数据,所以,在共享数据很大,且读操作远多于写操作的情况下,读写锁值得一试。
ReadWriteLock源码如下:
public interface ReadWriteLock {
//返回用于读取操作的锁。
Lock readLock();
//返回用于写入操作的锁。
Lock writeLock();
}
从源码中可以看到,ReadWriteLock并不是Lock的子接口。所以ReadWriteLock并没有Lock的那些特性。
在使用某些种类的Collection时,可以使用ReentrantReadWriteLock来提高并发性。通常,在预期collection很大,读取者线程访问它的次数多于写入者线程,并且entail操作的开销高于同步开销时,这很值得一试。例如,以下是一个使用 TreeMap的类,预期它很大,并且能被同时访问。
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.xml.crypto.Data;
public class RWDictionary {
private final Map<String, Data> m = new TreeMap<String, Data>();
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
public Data get(String key) {
r.lock();
try {
return m.get(key);
} finally {
r.unlock();
}
}
public String[] allKeys() {
r.lock();
try {
return (String[])m.keySet().toArray();
} finally {
r.unlock();
}
}
public Data put(String key, Data value) {
w.lock();
try {
return m.put(key, value);
} finally {
w.unlock();
}
}
public void clear() {
w.lock();
try {
m.clear();
} finally {
w.unlock();
}
}
}
特性
ReentrantReadWriteLock具有以下特性:(有待详细介绍)
- 公平性
- 重入性
- 锁降级
- 锁获取中断
- 支持Condition
- 检测系统状态
优点
与互斥锁相比,虽然一次只能有一个写线程可以修改共享数据,但大量读线程可以同时读取共享数据。在共享数据很大,且读操作远多于写操作的情况下,ReentrantReadWriteLock值得一试。
缺点
只有当前没有线程持有读锁或者写锁时才能获取到写锁,这可能会导致写线程发生饥饿现象,即读线程太多导致写线程迟迟竞争不到锁而一直处于等待状态。StampedLock可以解决这个问题,解决方法是如果在读的过程中发生了写操作,应该重新读而不是直接阻塞写线程。
本文就讲到这里,想了解Java并发编程更多内容请参考:
- Java并发编程札记-目录