По материалам Read/Write Locks in Java - Write Reentrance.
Одновременно может быть только один поток, который изменяет данные. Никаких других потоков (читающих или пишущих) быть не может. Если мы не предусмотрим повторное блокирование, то может возникнуть следующая ситуация (похожая на ситуацию с повторным блокированием при чтении данных):
- Поток получает доступ на запись
- Поток повторно запрашивает доступ на запись
- Из-за того, что счетчик writers отличен от нуля, поток переводится в состояние ожидания и зависает насовсем
Чтобы этого избежать, нужно при каждом запросе на запись проверять, имеет ли уже данный поток доступ на запись. Если он уже имеет доступ на запись, то мы можем дать ему повторное разрешение.
В коде ниже будем учитывать также повторную блокировку для читающих потоков. Уберем проверку условия на то, можем ли мы дать доступ на запись в отдельный метод - canGrantWriteAccess. В счетчик writers будем записывать число повторных блокировок. Теперь, если writers >0 мы будем проверять, имеет ли этот поток доступ на запись. Если поток имеет доступ на запись, то увеличиваем счетчик writers. Если поток не имеет доступа на запись, то по прежнему переводим его в режим ожидания.
Метод canGrantWriteAccess выглядит так:
private boolean canGrantWriteAccess(Thread callingThread){
if(hasReaders()) return false;
if(writingThread == null) return true;
if(!isWriter(callingThread)) return false;
return true;
}
Полный код класса таков:
public class ReadWriteLock {
private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>();
private int writers = 0;
private int writeRequests = 0;
private Thread writingThread;
//
// read locking
//
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();
int accessCount = getReadAccessCount(callingThread);
if(accessCount == 1) {
readingThreads.remove(callingThread);
} else {
readingThreads.put(callingThread, accessCount-1);
}
notifyAll();
}
private boolean canGrantReadAccess(Thread callingThread) {
if(writers > 0) return false;
if(isReader(callingThread) return true;
if(writeRequests > 0) return false;
return true;
}
private int getReadAccessCount(Thread callingThread) {
Integer accessCount = readingThreads.get(callingThread);
if(accessCount == null) return 0;
return accessCount.intValue();
}
private boolean isReader(Thread callingThread){
return readingThreads.get(callingThread) != null;
}
private boolean hasReaders(){
return readingThreads.size() > 0;
}
//
// write locking
//
public synchronized void lockWrite() throws InterruptedException {
writeRequests++;
Thread callingThread = Thread.currentThread();
while(!canGrantWriteAccess(callingThread)){
wait();
}
writeRequests--;
writers++;
writingThread = callingThread;
}
public synchronized void unlockWrite() throws InterruptedException{
writers--;
if(writers == 0){
writingThread = null;
}
notifyAll();
}
private boolean canGrantWriteAccess(Thread callingThread){
if(hasReaders()) return false;
if(writingThread == null) return true;
if(!isWriter(callingThread)) return false;
return true;
}
private boolean isWriter(Thread callingThread){
return writingThread == callingThread;
}
}
Здесь есть учет повторной блокировки для читающих потоков и учет повторной блокировки для записывающего потока. Мы еще не рассмотрели два случая: когда читающий поток хочет получить повторную блокировку на запись, и когда записывающий поток хочет получить повторную блокировку на чтение данных. Эти случаи будут рассмотрены в следующих постах.
Комментариев нет:
Отправить комментарий