1.先以一段代码为例
ReadWriteLock rwLock = new ReentrantReadWriteLock(); // rwLock.readLock()是获取rwLock里的一个属性而非new // 这句代码的意思是给rwLock加读锁,而不应该理解为给rwLock.readLock()的返回值加锁 // 这点Go比java好,go里读写锁就是提供四个方法,而java里拆成两个属性来提供让人误解 rwLock.readLock().lock();
2.读写锁对象里的读锁属性rwLock.readLock()和写锁属性rwLock.writeLock()里加锁或者释放锁都是针对rwLock的而非针对其读锁属性或写锁属性;
3.之前有个错误的记忆,就是读写锁rwLock如果加了读锁后,加写锁会被阻塞,此时再给rwLock加读锁是不会成功的,这个记忆是错误的;rwLock只要当前状态是已经被加了读锁状态,不管此时是否存在某个线程要给rwLock加写锁(会被阻塞)
,再给它加读锁都是成功的;
4.每次加锁都会保存一个锁数据,这个锁数据里包含了线程信息;java里在A线程里加了某个锁(包括普通锁和读写锁),那么一定要在该线程释放对应的锁(如普通锁,或者读写锁的读锁或写锁);(Go里没有这个要求,在A线程加锁,可以在B线程释放锁,而且Go的锁不是可重入的)
5.加了多少次锁就要释放多少次锁,比如普通lock在一个代码块里lock了两次,那么就必须unlock两次,否则其他地方仍然是无法获得锁的;这个同样适用于读锁,比如rwLock加了3次读锁,这个时候写锁被阻塞,必须释放三次读锁写锁才会获得加锁的权限;
6.rwLock加了写锁后,然后存在一个加写锁的被阻塞和加读锁的被阻塞的两个线程,此时释放rwLock的写锁,然后这两个阻塞的线程会公平的去“争抢”rwLock的写锁或读锁(而不是因为之前的是写锁,所以下一个一定是优先写锁或读锁)
,经过测试,到底是谁“争抢”成功,主要看这两个线程哪个先运行到rwLock加读/写锁的那句代码,先被阻塞的会先被唤醒去获得锁(当然不同的JVM可能不一样,所以这个不能当定论,只能说我的JVM测试结果是这样);
7.读写锁在一般用于读次数远远大于写次数的情况,但是如果说长时间都是一直存在读线程加读锁,那么就会造成写饥饿,即写锁线程一直没法加写锁,这个没有好的解决方法,一般就是将锁的fair设置为true,不过这样锁的效率又会降低很多。