一、读写锁简介
如果对于资源的读操作次数远大于写操作次数那么使用读写锁可以提高性能,否则不仅增加了系统复杂性且没有性能优势。
可读的前提是资源没有被写占有;
可写的前提是资源没有被读占有且没有被写占有
二、读写权限和优先级
1、什么条件下线程肯定有资格获取读权限
(1)该线程已经获得写权限
(2)该线程已经获得读权限(读可重入)
2、什么条件下线程肯定没有资格获取读权限
其它线程占有写权限
3、更高的写请求优先级
由于读操作更加频繁,如果不提高写操作的抢占优先级的话,那么请求写操作的线程会陷入“饥饿”,所以一个线程是否被允许可读不仅取决于当前有没有线程正在写,还取决于是否存在请求写的挂起的线程。因此,如果一个线程不满足“肯定有资格获取读权限”的条件且当前有资源的写请求者,那么该线程没有资格获得读权限。
4、什么条件下线程肯定有资格获取写权限
(1)该线程是唯一一个占有读权限的线程
(2)该线程已经获得写权限(写可重入)
5、什么条件下线程肯定没有资格获取写权限
其它线程占有读权限
其它线程占有写权限
public class ReadWriteLock {
private Map<Thread, Integer> readingThreads = new HashMap<>();
private int writeAccesses = 0;
private int writeRequests = 0;
private Thread writingThread = null;
public synchronized void lockRead() throws InterruptedException {
Thread callingThread = Thread.currentThread();
while (!canGrantReadAccess(callingThread)) {//循环可以避免假醒
this.wait();
}
//获得读锁,更新map
readingThreads.put(callingThread, getReadAccessCount(callingThread) + 1);
}
public synchronized void unlockRead() {
Thread callingThread = Thread.currentThread();
if (!isReader(callingThread)) {
throw new IllegalMonitorStateException("calling thread does not hold a read lock on this ReadWriteLock");
}
int accessCount = getReadAccessCount(callingThread);
if (accessCount == 1) {
readingThreads.remove(callingThread);
} else {
readingThreads.put(callingThread, accessCount - 1);
}
notifyAll();//~
}
public synchronized void lockWrite() throws InterruptedException {
writeRequests++;
Thread callingThread = Thread.currentThread();
while (!canGrantWriteAccess(callingThread)) {
this.wait();
}
writeRequests--;
writeAccesses++;
writingThread = callingThread;
}
private boolean canGrantReadAccess(Thread callingThread) {
if (isWriter(callingThread)) return true; //已经获得写权限,自然就可以拥有读权限
if (hasWriter()) return false;//写锁是独占的,别的线程在写,此线程没有读权限
if (isReader(callingThread)) return true;//放在hasWriteRequests()前面,防止写锁线程抢占
if (hasWriteRequests()) return false;//如果该线程没有拥有读锁或是写锁,那么它是竞争不过请求写的线程的
return true;
}
private boolean canGrantWriteAccess(Thread callingThread) {
if (isOnlyReader(callingThread)) return true;//唯一的读者可以写
if (hasReaders()) return false;//有线程占有读,不可写
if (!hasWriter()) return true;//没有线程占有读或者写,可写
if (!isWriter(callingThread)) return false;//有线程占有写,但不是自己,不可写
return true;
}
//当前线程是否拥有写锁
private boolean isWriter(Thread callingThread) {
return writingThread == callingThread;
}
private boolean hasWriter() {
return writingThread != null;
}
private boolean hasWriteRequests() {
return writeRequests > 0;
}
private boolean hasReaders() {
return !readingThreads.isEmpty();
}
private boolean isReader(Thread callingThread) {
return readingThreads.get(callingThread) != null;
}
//判断当前线程是不是唯一的读者,如果是,那么它就有权限获取写锁
private boolean isOnlyReader(Thread callingThread) {
return readingThreads.size() == 1 && readingThreads.get(callingThread) != null;
}
private int getReadAccessCount(Thread callingThread) {
Integer accessCount = readingThreads.get(callingThread);
if (accessCount == null) {
return 0;
}
return accessCount.intValue();
}
}
注意:代码中唤醒等待线程使用的是notifyAll()而不是notify(),考虑下面两种情况:
1、如果有多个读线程在等待读锁且没有线程在等待写锁时,调用unlockWrite()后,所有等待读锁的线程都能立马成功获取读锁 —— 而不是一次只允许一个
2、如果使用notify()每次只有一个线程被唤醒,如果该线程是读线程,它请求读取权限,结果失败,理由是有写请求(写的优先级更高),但是等待写的线程还在挂起状态,没法抢占锁。这样就等于一次信号丢失。