1、什么是读写锁ReentrantReadWriter
说到ReentrantReadWriteLock,首先要做的是与ReentrantLock划清界限。它和后者都是单独的实现,彼此之间没有继承或实现的关系。
ReentrantLock 实现了标准的互斥操作,属于排他锁,也就是一次只能有一个线程持有锁,也即所谓独占锁的概念。显然这个特点在一定程度上面减低了吞吐量,实际上独占锁是一种保守的锁策略,在这种情况下任何“读/读”,“写/读”,“写/写”操作都不能同时发生。但是同样需要强调的一个概念是,锁是有一定的开销的,当并发比较大的时候,锁的开销就比较客观了。所以如果可能的话就尽量少用锁,非要用锁的话就尝试看能否改造为读写锁。
ReadWriteLock 描述的是:一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。也就是说读写锁使用的场合是一个共享资源被大量读取操作,而只有少量的写操作(修改数据)。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。
2、读写锁ReentrantReadWriter特性
公平性选择:支持非公平(默认)和公平的锁获取模式,吞吐量还是非公平优于公平
重入性:该锁支持重入锁,以读写线程为例:读线程在获取读锁之后,能够再次读取读锁,而写线程在获取写锁之后可以同时再次获取读锁和写锁
锁降级:遵循获取写锁,获取读锁再释放写锁的次序,写锁能够降级为读锁
3、读写锁ReadWriteLock的API
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*/
Lock readLock();
/**
* Returns the lock used for writing.
*/
Lock writeLock();
}
4、实现AB互斥、AC互斥、BC不互斥
思考这样一个需求
一个java类里面有ABC三个方法,我想在多线程的情况下,AB互斥、AC互斥、BC不互斥,有什么办法做到吗?
用读写锁实现的思路很简单,就是 B和C加上读锁,A加上写锁,就可以了,这样BC可以同时走,但B或C执行时,A不能执行,反之也是
private static final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private static final Lock readLock = rwLock.readLock(); // 读锁
private static final Lock writeLock = rwLock.writeLock();// 写锁
public static void A() {
try {
writeLock.lock();
// do something...
} finally {
writeLock.unlock();
}
}
public static void B() {
try {
readLock.lock();
// do something...
} finally {
readLock.unlock();
}
}
public static void C() {
try {
readLock.lock();
// do something...
} finally {
readLock.unlock();
}
}
思考一个问题,我们默认使用的锁是非公平锁,只有当我们显示设置为公平锁的情况下,才会使用公平锁。看读写锁的实现源码,我们可以发现,读锁和写锁共用同一个等待队列,那么在采用非公平锁的情况下,如果读锁的线程执行时间比较长,并且读锁的并发比较高,那么写锁的线程是有可能永远都拿不到锁的。这个时候就需要使用公平锁了,公平锁可以实现FIFO,如果等待队列中没有节点在等待,则占有锁,如果已经存在等待节点,则返回失败,由后面的程序去将此线程加入等待队列。但是,使用公平锁的线程进行了频繁切换,而频繁切换线程,性能必然会下降的厉害,这也告诫了我们在实际的开发过程中,在需要使用公平锁的情景下,务必要考虑线程的切换频率。