一、读写锁
我们知道在多个线程访问同一个数据的时候是存在线程安全问题的,而在仅仅是读取数据的时候,是没有安全问题的,那么多个线程同时读取数据我们就可以让其不互斥;而多个线程都在修改(写)数据或有的在读取有的在写入的时候再让其互斥,这样不但保证线程安全而且提高性能。
ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。
与互斥锁(读读互斥,读写互斥)相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程),读-写锁利用了这一点。从理论上讲,与互斥锁相比,使用读-写锁所允许的并发性增强将带来更大的性能提高。
二、读写锁特性
1.重入
此锁允许 reader 和 writer 按照 ReentrantLock 的样式重新获取读取锁或写入锁。在写入线程保持的所有写入锁都已经释放后,才允许重入 reader 使用它们。
此外,writer 可以获取读取锁,但反过来则不成立。在其他应用程序中,当在调用或回调那些在读取锁状态下执行读取操作的方法期间保持写入锁时,重入很有用。如果 reader 试图获取写入锁,那么将永远不会获得成功。
2.锁降级
重入还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不可能的。
3.锁获取的中断
读取锁和写入锁都支持锁获取期间的中断。
4.Condition 支持
写入锁提供了一个 Condition 实现,对于写入锁来说,该实现的行为与 ReentrantLock.newCondition() 提供的 Condition 实现对 ReentrantLock 所做的行为相同。当然,此 Condition 只能用于写入锁。
读取锁不支持 Condition,readLock().newCondition() 会抛出 UnsupportedOperationException。
package com.dason.second;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 模拟高并发从缓存中获取数据,若缓存中没有,则查询数据库
* @author Dason
*
*/
public class CacheDemo {
private Map<String, Object> cache = new HashMap<String, Object>();
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
new Thread() {
public void run() {
Object obj = new CacheDemo().getData("a");
System.out.println(obj);
};
}.start();
}
public Object getData(String key){
//1.加上读锁
rwl.readLock().lock();
Object value = null;
try{
value = cache.get(key);
if(value == null){
//2.若缓存中没有数据,释放读锁,
rwl.readLock().unlock();
//3.加上写锁.若多个线程进来执行到这一步,一个上锁成功,其他线程阻塞
//与解决单例模中懒汉模式的线程安全的方式一样
rwl.writeLock().lock();//①
try{
//3.这个判断避免多个线程进来执行①时后重复查询数据库
if(value==null){
//为value 赋值,即写操作.
value = "aaaa";//实际是去queryDB();
}
//4.锁降级,写锁变为读锁.
rwl.readLock().lock();
}finally{
rwl.writeLock().unlock();
}
}
//使用value,这里不提供具体实现,因为其不是重点
//use(value);
}finally{
rwl.readLock().unlock();
}
return value;
}
}