java 다중 스레드 - 읽기와 쓰기 잠금 원리
먼저 읽기/쓰기 액세스 리소스의 조건에 대한 개요를 살펴보겠습니다.
읽기 스레드가 없고 쓰기 작업을 요청하는 스레드가 없습니다.
쓰기에 스레드가 없습니다. 읽기와 쓰기 작업을 하고 있습니다.
만약 어떤 스레드가 자원을 읽으려고 한다면, 스레드가 이 자원에 대한 쓰기 작업을 하고 있고, 스레드가 이 자원에 대한 쓰기 작업을 요청하지 않으면 된다.읽기 작업에 대한 요청보다 쓰기 작업에 대한 요청이 더 중요하다고 가정하면 쓰기 요청의 우선순위를 높여야 한다.그 밖에 읽기 조작이 빈번하게 발생하고 우리가 쓰기 조작의 우선순위를 높이지 않으면'배고픔'현상이 발생한다.모든 읽기 스레드가 ReadWriteLock에서 잠금이 해제될 때까지 쓰기 작업을 요청하는 스레드가 계속 막힙니다.만약 새로운 스레드의 읽기 조작 권한을 계속 보장한다면, 쓰기 조작을 기다리는 스레드가 계속 막힐 것이다. 결과적으로 배고픔이 발생하는 것이다.따라서 ReadWriteLock을 잠그고 쓰기 작업을 하고 있는 루틴이 없고 쓰기 작업을 수행할 준비가 되어 있는 루틴이 없을 때만 읽기 작업이 계속될 수 있습니다.
다른 스레드가 공유 자원에 대한 읽기 작업이나 쓰기 작업이 없을 때, 어떤 스레드는 이 공유 자원의 쓰기 자물쇠를 얻어 공유 자원에 대한 쓰기 작업을 할 수 있다.자물쇠 작성을 얼마나 요청했는지, 그리고 어떤 순서로 자물쇠를 요청했는지는 중요하지 않다. 자물쇠 작성의 공평성을 보장하고 싶지 않으면.
위의 서술에 따라 간단하게 읽기/쓰기 자물쇠를 실현하고 코드는 다음과 같다.
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()
throws InterruptedException{
writers--;
notifyAll();
}
}
ReadWriteLock 클래스에서 자물쇠를 읽고 쓰는 방법은 각각 자물쇠를 가져오고 놓는 방법이 있습니다.읽기 자물쇠의 실현은lockRead()에서 루트가 없으면 쓰기 자물쇠 (writers=0) 를 가지고 있고, 루트가 쓰기 자물쇠 (writeRequests==0) 를 요청하지 않으면 읽기 자물쇠를 얻으려는 모든 루트를 성공적으로 얻을 수 있습니다.
자물쇠 쓰기의 실현은 lockWrite ()에서 하나의 스레드가 자물쇠를 얻으려고 할 때 먼저 자물쇠 쓰기 요청 수를 1(writeRequests++) 추가한 다음에 자물쇠를 정말 얻을 수 있는지 판단한다. 스레드가 읽기 자물쇠 (readers=0) 를 가지고 있지 않고 자물쇠 (writers=0) 를 가지고 있지 않을 때 자물쇠를 쓸 수 있다.몇 개의 라인이 자물쇠를 요청하고 있는지 상관없다.
주의해야 할 것은 자물쇠를 놓는 두 가지 방법 (unlockRead, unlockWrite) 에서 notifyAll 방법이 아닌 notifyAll 방법이 호출되었다는 것이다.이 원인을 설명하려면 다음과 같은 상황을 상상할 수 있다.
만약 루트가 읽기 자물쇠를 가져오기를 기다리고 있다면, 동시에 루트가 쓰기 자물쇠를 가져오기를 기다리고 있습니다.만약 이때 잠금 읽기를 기다리는 라인 중 하나가 notify 방법으로 깨어나지만, 잠금 쓰기를 요청하는 라인이 존재하기 때문에 (writeRequests>0) 깨어난 라인은 다시 막힌 상태로 들어갑니다.그러나 자물쇠를 쓰는 것을 기다리는 라인은 하나도 깨어나지 않고 아무 일도 없었던 것처럼.notifyAll 방법을 사용하면 모든 라인이 깨어나서 요청한 자물쇠를 얻을 수 있는지 판단합니다.
notify All로 좋은 점이 하나 더 있습니다.만약 여러 개의 읽기 스레드가 읽기 자물쇠를 기다리고 있고 쓰기 자물쇠를 기다리는 스레드가 없다면, unlockWrite () 를 호출하면 읽기 자물쇠를 기다리는 모든 스레드가 한 번에 하나만 허용하는 것이 아니라 읽기 자물쇠를 가져오는 데 성공할 수 있습니다.
읽기/쓰기 자물쇠 재입력
위에서 이루어진 읽기/쓰기 자물쇠는 다시 들어갈 수 없습니다. 이미 쓰기 자물쇠를 가지고 있는 라인이 다시 쓰기 자물쇠를 요청하면 막힙니다.왜냐하면 이미 쓰기 라인이 하나 있기 때문이다. 바로 그 자신이다.또한 다음 예제를 고려하십시오.
읽기 잠금 재입력
ReadWriteLock의 읽기 자물쇠를 다시 입력할 수 있도록 읽기 자물쇠 재입력에 대한 규칙을 설정합니다.
어떤 라인에 있는 읽기 자물쇠가 다시 들어갈 수 있는지 확인하거나, 읽기 자물쇠를 가져오는 조건을 충족시키거나, 쓰기 요청이 있든 없든지, 읽기 자물쇠를 가지고 있는지 확인하십시오.하나의 스레드가 읽기 자물쇠를 가지고 있는지 확인하려면 맵으로 이미 읽기 자물쇠를 가지고 있는 스레드와 대응하는 스레드가 읽기 자물쇠를 얻는 횟수를 저장할 수 있으며, 어떤 스레드가 읽기 자물쇠를 얻을 수 있는지 판단할 때 맵에 저장된 데이터를 이용하여 판단할 수 있다.다음은 메서드 lockRead와 unlockRead가 수정한 코드입니다.
public class ReadWriteLock{
private Map<Thread, Integer> readingThreads =
new HashMap<Thread, Integer>();
private int writers = 0;
private int writeRequests = 0;
public synchronized void lockRead()
throws InterruptedException{
Thread callingThread = Thread.currentThread();
while(! canGrantReadAccess(callingThread)){
wait();
}
readingThreads.put(callingThread,
(getAccessCount(callingThread) + 1));
}
public synchronized void unlockRead(){
Thread callingThread = Thread.currentThread();
int accessCount = getAccessCount(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;
}
}
코드에서 알 수 있듯이 루트가 없는 경우에만 자물쇠의 재입력을 읽을 수 있습니다.이 밖에 다시 들어오는 읽기 자물쇠는 쓰기 자물쇠보다 우선순위가 높다.쓰기 자물쇠 재입력
한 라인이 쓰기 자물쇠를 가지고 있어야만 쓰기 자물쇠를 다시 입력할 수 있습니다.다음은 lockWrite와 unlockWrite가 수정한 코드입니다.
public class ReadWriteLock{
private Map<Thread, Integer> readingThreads =
new HashMap<Thread, Integer>();
private int writeAccesses = 0;
private int writeRequests = 0;
private Thread writingThread = null;
public synchronized void lockWrite()
throws InterruptedException{
writeRequests++;
Thread callingThread = Thread.currentThread();
while(!canGrantWriteAccess(callingThread)){
wait();
}
writeRequests--;
writeAccesses++;
writingThread = callingThread;
}
public synchronized void unlockWrite()
throws InterruptedException{
writeAccesses--;
if(writeAccesses == 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 hasReaders(){
return readingThreads.size() > 0;
}
private boolean isWriter(Thread callingThread){
return writingThread == callingThread;
}
}
현재 스레드가 쓰기 자물쇠를 가져올 수 있는지 확인할 때 어떻게 처리하는지 주의하십시오.읽기 잠금을 쓰기 잠금으로 업그레이드
때때로, 우리는 자물쇠를 읽는 라인을 가지고 있고, 자물쇠를 쓸 수 있기를 바란다.이러한 조작을 허용하려면 이 스레드가 읽기 자물쇠를 가진 유일한 스레드여야 합니다.writeLock()은 이 목적을 달성하기 위해 약간의 변경이 필요합니다.
public class ReadWriteLock{
private Map<Thread, Integer> readingThreads =
new HashMap<Thread, Integer>();
private int writeAccesses = 0;
private int writeRequests = 0;
private Thread writingThread = null;
public synchronized void lockWrite()
throws InterruptedException{
writeRequests++;
Thread callingThread = Thread.currentThread();
while(!canGrantWriteAccess(callingThread)){
wait();
}
writeRequests--;
writeAccesses++;
writingThread = callingThread;
}
public synchronized void unlockWrite() throws InterruptedException{
writeAccesses--;
if(writeAccesses == 0){
writingThread = null;
}
notifyAll();
}
private boolean canGrantWriteAccess(Thread callingThread){
if(isOnlyReader(callingThread)) return true;
if(hasReaders()) return false;
if(writingThread == null) return true;
if(!isWriter(callingThread)) return false;
return true;
}
private boolean hasReaders(){
return readingThreads.size() > 0;
}
private boolean isWriter(Thread callingThread){
return writingThread == callingThread;
}
private boolean isOnlyReader(Thread thread){
return readers == 1 && readingThreads.get(callingThread) != null;
}
}
이제 ReadWriteLock 클래스는 읽기 잠금에서 쓰기 잠금으로 업그레이드할 수 있습니다.쓰기 잠금을 읽기 잠금으로 낮춥니다.
때때로 자물쇠를 쓰는 라인을 가지고 있어도 자물쇠를 읽기를 원한다.만약 한 라인에 쓰기 자물쇠가 있다면, 자연히 다른 라인에는 읽기 자물쇠나 쓰기 자물쇠가 있을 수 없다.그래서 자물쇠를 쓰는 라인을 가지고 다시 자물쇠를 읽는 것은 위험하지 않다.위의 canGrantReadAccess 방법에 대한 간단한 수정만 필요합니다.
public class ReadWriteLock{
private boolean canGrantReadAccess(Thread callingThread){
if(isWriter(callingThread)) return true;
if(writingThread != null) return false;
if(isReader(callingThread) return true;
if(writeRequests > 0) return false;
return true;
}
}
재입력 가능한 ReadWriteLock의 전체 구현다음은 전체 ReadWriteLock 구현입니다.코드의 읽기와 이해에 편리하도록 위의 코드를 간단하게 재구성했다.재구성된 코드는 다음과 같다.
public class ReadWriteLock{
private Map<Thread, Integer> readingThreads =
new HashMap<Thread, Integer>();
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)){
wait();
}
readingThreads.put(callingThread,
(getReadAccessCount(callingThread) + 1));
}
private boolean canGrantReadAccess(Thread callingThread){
if(isWriter(callingThread)) return true;
if(hasWriter()) return false;
if(isReader(callingThread)) return true;
if(hasWriteRequests()) return false;
return true;
}
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)){
wait();
}
writeRequests--;
writeAccesses++;
writingThread = callingThread;
}
public synchronized void unlockWrite()
throws InterruptedException{
if(!isWriter(Thread.currentThread()){
throw new IllegalMonitorStateException(
"Calling Thread does not" +
" hold the write lock on this ReadWriteLock");
}
writeAccesses--;
if(writeAccesses == 0){
writingThread = null;
}
notifyAll();
}
private boolean canGrantWriteAccess(Thread callingThread){
if(isOnlyReader(callingThread)) return true;
if(hasReaders()) return false;
if(writingThread == null) return true;
if(!isWriter(callingThread)) return false;
return true;
}
private int getReadAccessCount(Thread callingThread){
Integer accessCount = readingThreads.get(callingThread);
if(accessCount == null) return 0;
return accessCount.intValue();
}
private boolean hasReaders(){
return readingThreads.size() > 0;
}
private boolean isReader(Thread callingThread){
return readingThreads.get(callingThread) != null;
}
private boolean isOnlyReader(Thread callingThread){
return readingThreads.size() == 1 &&
readingThreads.get(callingThread) != null;
}
private boolean hasWriter(){
return writingThread != null;
}
private boolean isWriter(Thread callingThread){
return writingThread == callingThread;
}
private boolean hasWriteRequests(){
return this.writeRequests > 0;
}
}
finally에서 unlock () 호출ReadWriteLock을 사용하여 임계 영역을 보호할 때, 임계 구역에서 이상이 발생할 수 있다면,finally 블록에서 readUnlock () 과 writeUnlock () 를 호출하는 것이 중요합니다.이렇게 하는 것은 ReadWriteLock이 성공적으로 잠금 해제될 수 있도록 하기 위해서이며, 다른 라인에서 이 잠금을 요청할 수 있도록 하기 위해서이다.여기에는 다음과 같은 예가 있습니다.
lock.lockWrite();
try{
//do critical section code, which may throw exception
} finally {
lock.unlockWrite();
}
위의 코드 구조는 임계 구역에서 이상이 발생할 때 ReadWriteLock도 방출될 수 있도록 보장합니다.만약 unlockWrite 방법이finally 블록에서 호출되지 않는다면, 임계 구역에서 이상이 발생했을 때, ReadWriteLock은 계속 잠금 상태를 유지합니다. 모든 호출lockRead () 또는 lockWrite () 의 라인이 계속 막힙니다.유일하게 ReadWriteLock을 다시 잠금 해제할 수 있는 요소는 ReadWriteLock이 다시 들어갈 수 있다는 것이다. 이상을 던질 때, 이 라인은 다음에 이 자물쇠를 성공적으로 가져와서 임계구를 실행하고 unlockWrite () 를 다시 호출하면 ReadWriteLock을 다시 방출할 수 있다.하지만 이 라인이 다음에 이 자물쇠를 가져오지 않는다면?따라서finally에서 unlockWrite를 호출하는 것은 건장한 코드를 쓰는 데 매우 중요하다.이상은 자바 다중 라인에 대한 자료 정리입니다. 후속적으로 관련 자료를 계속 보충합니다. 본 사이트에 대한 지지에 감사드립니다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
38. Java의 Leetcode 솔루션텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.