多线程读写共享资源的规则:
1.已有线程在进行read时,当前线程的read操作不等待,而write需要等待
2.已有线程在进行write时,当前线程read或者write都需要等待
简单的读写锁实现:
public class ReadWriteLock {
private int readers = 0;
private int writers = 0;
private int writeRequests = 0;
public synchronized void lockRead() throws InterruptedException {
while (writers > 0 || writeRequests > 0) {
wait();
}
readers++;
}
public synchronized void unlockRead() {
readers--;
notifyAll();
}
public synchronized void lockWrite() throws InterruptedException {
writeRequests++;
while (readers > 0 || writers > 0) {
wait();
}
writeRequests--;
writers++;
}
public synchronized void unlockWrite() {
writers--;
notifyAll();
}
}
原文中的读写锁中优先写操作,在写操作的请求比较多的情况下会导致读取一直无法进行,所以可以添加一个标志位以切换优先级。
public class ReadWriteLock {
private int readers = 0;
private int writers = 0;
private int writeRequests = 0;
private boolean preferWriter = true;
public synchronized void lockRead() throws InterruptedException {
while (writers > 0 || (preferWriter && writeRequests > 0)) {
wait();
}
readers++;
}
public synchronized void unlockRead() {
readers--;
preferWriter = true;
notifyAll();
}
public synchronized void lockWrite() throws InterruptedException {
writeRequests++;
while (readers > 0 || writers > 0) {
wait();
}
writeRequests--;
writers++;
}
public synchronized void unlockWrite() {
writers--;
preferWriter = false;
notifyAll();
}
}
锁的重入问题
内置锁本身是可重入的,如果要在自己实现的简单独占锁中支持可重入,就需要一个计数器字段,否则在释放锁的时候无法知道线程是否在释放最后一次的锁定。
所以在读写锁中要记录已经获取锁定的线程数量,在同一时间最多只会有一个写线程获取写操作的锁定,所以只要一个字段记录当前的写线程。而在同时会有多个读线程获取锁定或者重入获取锁,必须要每个读线程获取锁的次数,否则在释放读锁的时候还是无法知道线程是否在释放最后一次的锁定。
锁重入时的规则:
1.已获取读锁时,写线程被迫等待,所以可以再次获取读锁,而且如果不允许再次获取的话,当前已有的读锁无法释放,会出现死锁的情形;
2。已获取写锁时,读线程都被迫等待,所以可以再次获取写锁,如果不允许再次获取的话,当前已有的写锁无法释放,也会出现死锁的情形。
实现可重入是在每次判断线程是否需要等待时新增了一种情况,当能确认是线程重入时则继续往下执行。
public class ReentrantReadWriteLock {
private int writers = 0;
private int writeRequests = 0;
private boolean preferWriter = true;
private Thread writingThread = null;
private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>();
public synchronized void lockRead() throws InterruptedException {
Thread callingThread=Thread.currentThread();
while (!canGrantReadAccess(callingThread)) {
wait();
}
int accessCount = getReadAccessCount(callingThread);
readingThreads.put(callingThread, accessCount + 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);
preferWriter = true;
} else {
readingThreads.put(callingThread, accessCount - 1);
}
notifyAll();
}
public synchronized void lockWrite() throws InterruptedException {
writeRequests++;
Thread callingThread=Thread.currentThread();
while (!canGrantWriteAccess(callingThread)) {
wait();
}
writeRequests--;
writers++;
writingThread = callingThread;
}
public synchronized void unlockWrite() {
Thread callingThread = Thread.currentThread();
if (!isWriter(callingThread)) {
throw new IllegalMonitorStateException("Calling thread does not hold a write lock on this ReadWriteLock");
}
writers--;
if (writers == 0) {
writingThread = null;
preferWriter = false;
}
notifyAll();
}
private boolean canGrantReadAccess(Thread callingThread) {
if (writers > 0)
return false;
if (isReader(callingThread))
return true;
if (preferWriter && writeRequests > 0) // 相对位置要在上一个判断条件之后,第一个判断条件的位置没有关系
return false;
return true;
}
private boolean canGrantWriteAccess(Thread callingThread) {
if (hasReaders())
return false;
if (isWriter(callingThread))
return true;
if (writers > 0) // 相对位置要在上一个判断条件之后,第一个判断条件的位置没有关系
return false;
return true;
}
private boolean hasReaders() {
return readingThreads.size() > 0;
}
private boolean isReader(Thread callingThread) {
return readingThreads.get(callingThread) != null;
}
private int getReadAccessCount(Thread callingThread) {
Integer accessCount = readingThreads.get(callingThread);
if (accessCount == null)
return 0;
return accessCount.intValue();
}
private boolean isWriter(Thread callingThread) {
return writingThread == callingThread;
}
}
读锁升级到写锁
已经获取读锁的线程再次获取写锁时,和普通的写锁一样,需要等到其他线程的锁定都释放,只有当前一个线程时才能够获取到。
private boolean canGrantWriteAccess(Thread callingThread) {
if (isOnlyReader(callingThread))
return true;
if (hasReaders())
return false;
if (isWriter(callingThread))
return true;
if (writers > 0)
return false;
return true;
}
private boolean isOnlyReader(Thread callingThread) {
return isReader(callingThread) && readingThreads.size() == 1;
}
写锁降级到读锁
已经获取到写锁,再去获取读锁时,不会有其他的线程获取到锁定,所以可以获取到读锁。
private boolean canGrantReadAccess(Thread callingThread) {
if (isWriter(callingThread))
return true;
if (writers > 0)
return false;
if (isReader(callingThread))
return true;
if (preferWriter && writeRequests > 0)
return false;
return true;
}
释放锁时需要使用notifyAll()方法,以防止信号丢失的问题。