上一篇里讲的ReentrankLock是一种排他锁,即同一时间只能有一个线程进入。而读写锁在同一时刻允许多个读线程访问,但是在写线程访问时,所有的读线程和其他线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读写锁,使得并发性比一般的排它锁有了很大提升。因为大多数应用场景都是读多于写的,因此在这样的情况下,读写锁可以提高吞吐量。下图描述了关于读写锁的三个特性:公平性、重入性和锁降级。
1 读写锁的使用
现在有这样一个需求,需要构造一个数据结构作为一个系统中的临时缓存,需要保证线程安全。我们在这里就可以利用读写锁来实现。
/**
* 利用读写锁实现的一个数据结构
* @author songxu
*
*/
public class ReadWriteCache
{
//利用hashmap作为底层数据结构
private Map<String, Object> cache=new HashMap<String, Object>();
//构造读写锁
private ReentrantReadWriteLock readwritelock=new ReentrantReadWriteLock();
//读锁
private Lock readLock=readwritelock.readLock();
//写锁
private Lock writeLock=readwritelock.writeLock();
/**
* 存入数据
* @param key 键
* @param value 值
*/
public void put(String key,Object value)
{
writeLock.lock();
//锁一定在try块之外
try {
cache.put(key, value);
}
finally
{
writeLock.unlock();
}
}
/**
* 获取数据
* @param key 键
* @return 值
*/
public Object get(String key)
{
readLock.lock();
try {
return cache.get(key);
}
finally
{
readLock.unlock();
}
}
}
在上述代码中,put方法在更新或插入数据前必须提前获取写锁,当获取写锁之后,其他线程对于读锁和写锁的获取均被阻塞,只有写锁释放后,其他读操作才能继续。在get方法中,需要获取读锁,而此时其他线程均可访问该方法而不被阻塞。可以说这是一个类似于concurrentHashMap的原型,但它的效率肯定没有concurrentHashMap高,但应该比HashTable要强一些
。
2 读写锁的实现原理
2.1 读写状态
读写锁同样利用同步器实现锁的功能,在ReetrantLock中,同步状态表示锁被一个线程重复获取的次数,而读写锁的自定义同步器需要在同步状态上维护多个读线程和一个写线程的状态。如果想在一个整型变量上维护这样一个状态,那么采用按位分割的方式是一个不错的选择。如下图所示,将一个变量分为两个部分,高16位表示读,低16位表示写