2020 Java 동시 및 다중 스레드 자습서(19): 파이프라인 잠금 해제

번역:GentlemanTsao, 2020-05-30
문서 목록
  • 파이프라인 잠금이 어떻게 발생했는지
  • 보다 현실적인 예
  • 파이프라인 잠금사 vs 잠금사
  • 번역 에피소드
  • 플러그 튜브 잠금이 어떻게 발생하는지
    파이프를 끼워 넣는 것은 자물쇠가 죽는 것과 유사하다.네스트된 파이프 스레드 잠금이 이렇게 발생합니다.
    Thread 1 synchronizes on A
    Thread 1 synchronizes on B (while synchronized on A)
    Thread 1 decides to wait for a signal from another thread before continuing
    Thread 1 calls B.wait() thereby releasing the lock on B, but not A.
    
    Thread 2 needs to lock both A and B (in that sequence)
            to send Thread 1 the signal.
    Thread 2 cannot lock A, since Thread 1 still holds the lock on A.
    Thread 2 remain blocked indefinately waiting for Thread1
            to release the lock on A
    
    Thread 1 remain blocked indefinately waiting for the signal from
            Thread 2, thereby
            never releasing the lock on A, that must be released to make
            it possible for Thread 2 to send the signal to Thread 1, etc.

    이것은 이론적인 상황처럼 들리지만 다음 Lock의 직접적인 실현을 보십시오.
    //         lock  
    
    public class Lock{
      protected MonitorObject monitorObject = new MonitorObject();
      protected boolean isLocked = false;
    
      public void lock() throws InterruptedException{
        synchronized(this){
          while(isLocked){
            synchronized(this.monitorObject){
                this.monitorObject.wait();
            }
          }
          isLocked = true;
        }
      }
    
      public void unlock(){
        synchronized(this){
          this.isLocked = false;
          synchronized(this.monitorObject){
            this.monitorObject.notify();
          }
        }
      }
    }

    lock () 방법은 "this"에서 동기화한 다음 모니터 Object 구성원 변수에서 동기화하는 것을 주의하십시오.isLocked가 false인 경우 문제가 없습니다.이 스레드는 모니터Object를 호출하지 않습니다.wait(). 단, isLocked가true라면, lock () 를 호출하는 루트는 모니터 Object에 멈추게 됩니다.wait () 호출 중입니다.
    문제는 모니터 Object를 호출하는 것입니다.wait () 는 모니터 Object 구성원의 동기화 프로세스만 방출하고'this'와 관련된 동기화 프로세스는 방출하지 않습니다.다시 말하면 대기 상태에 멈춘 라인은'this'의 동기화 자물쇠를 가지고 있다.
    Lock을 잠근 첫 번째 스레드가 unlock () 을 호출하여 잠금을 해제하려고 시도할 때, unlock () 방법의 synchronized (this) 코드 블록에 들어가려고 시도할 때 막힙니다.lock () 에서 기다리는 라인이synchronized (this) 를 떠날 때까지 차단 상태를 유지합니다.단, lock () 방법에서 기다리는 루틴은 isLocked가false로 설정되고 모니터Object를 실행할 때까지 이 블록을 떠나지 않습니다.notify (), 이것이 바로 unlock () 입니다.
    간단히 말하면, lock () 를 기다리는 라인은 lock () 과 동기화 블록을 종료하기 위해 unlock () 를 성공적으로 실행해야 한다.단, lock () 에서 기다리는 루틴이 외부 동기화 블록을 떠나기 전에는 unlock () 을 실행할 수 있는 루틴이 없습니다.
    결과적으로, lock () 또는 unlock () 를 호출하는 모든 라인이 영원히 막힐 것입니다.이것은 파이프를 끼워 넣는 자물쇠사라고 부른다.
    더욱 현실적인 예
    너는 영원히 위의 방식대로 자물쇠를 실현하지 못할 것이라고 말할 수 있다.내부 관리 대상에서wait()와 notify()를 호출하지 않고this에서 호출하기 때문이다.그럴 가능성이 높습니다.그러나 어떤 경우 상술한 디자인이 나타날 수 있다.예를 들어 Lock에서 공평성을 실현하려면이렇게 할 때, 한 번에 한 라인을 알릴 수 있도록 각 라인이 각자의 대기열 대상에서wait () 를 호출하기를 바랍니다.
    이런 공평한 자물쇠의 간단한 실현을 살펴보자.
    //Fair Lock implementation with nested monitor lockout problem
    
    public class FairLock {
      private boolean           isLocked       = false;
      private Thread            lockingThread  = null;
      private List<QueueObject> waitingThreads =
                new ArrayList<QueueObject>();
    
      public void lock() throws InterruptedException{
        QueueObject queueObject = new QueueObject();
    
        synchronized(this){
          waitingThreads.add(queueObject);
    
          while(isLocked || waitingThreads.get(0) != queueObject){
    
            synchronized(queueObject){
              try{
                queueObject.wait();
              }catch(InterruptedException e){
                waitingThreads.remove(queueObject);
                throw e;
              }
            }
          }
          waitingThreads.remove(queueObject);
          isLocked = true;
          lockingThread = Thread.currentThread();
        }
      }
    
      public synchronized void unlock(){
        if(this.lockingThread != Thread.currentThread()){
          throw new IllegalMonitorStateException(
            "Calling thread has not locked this lock");
        }
        isLocked      = false;
        lockingThread = null;
        if(waitingThreads.size() > 0){
          QueueObject queueObject = waitingThreads.get(0);
          synchronized(queueObject){
            queueObject.notify();
          }
        }
      }
    }
    public class QueueObject {}

    언뜻 보기에는 이 실현이 괜찮은 것 같지만, lock () 방법은queueObject를 호출했습니다.wait (), 이 호출은 두 동기화 블록 내부에 있습니다.하나는 "this"에서 동기화되고, 다른 하나는 그 안에 끼워 넣고queueObject 국부 변수에서 동기화됩니다.스레드가 queueObject를 호출할 때.wait ()일 때 QueueObject 실례의 자물쇠를 방출하지만 "this"와 관련된 자물쇠는 방출하지 않습니다.
    또한 unlock () 방법은synchronized로 성명되며, 이것은synchronized (this) 블록에 해당합니다.이것은 lock () 에서 라인이 기다리면'this' 와 관련된 파이프 대상이 이 기다린 라인에 잠긴다는 것을 의미합니다.unlock () 를 호출하는 모든 루틴은 'this' 에 대한 잠금이 풀리기를 기다립니다.그러나 이것은 영원히 발생하지 않을 것이다. 라인이 기다리는 라인에 신호를 성공적으로 보낼 때만 발생하고, 신호를 보내는 방법은 unlock () 방법을 통해서만 실행될 수 있기 때문이다.
    따라서 위의 FairLock이 실현되면 파이프라인 잠금이 끊어질 수 있습니다.'배고픔과 공평성'편에서 공평의 자물쇠를 어떻게 더 잘 실현할 수 있는지 설명했다.
    파이프라인 잠금 죽기 vs 잠금 죽기
    파이프를 끼워 넣은 자물쇠가 죽은 것과 죽은 자물쇠의 결과는 거의 같다. 관련된 루트는 결국 막혀서 영원히 상대방을 기다린다.
    그러나 이 두 가지 상황은 결코 등가가 아니다.사라진 자물쇠 편에서 말한 바와 같이 두 라인이 서로 다른 순서로 자물쇠를 얻었을 때 사라진 자물쇠가 발생한다.스레드 1 잠금 A, B를 기다립니다.스레드 2에서 B를 잠그고 A를 기다립니다.잠금 방지 편에서 말한 바와 같이 항상 같은 순서로 잠금(잠금 순서)을 하면 잠금이 사라지는 것을 피할 수 있다.그러나 플러그 튜브 잠금 사망은 두 라인이 같은 순서로 잠금되어 발생하는 것이다.스레드 1은 A와 B를 잠그고 B를 방출하고 스레드 2의 신호를 기다린다.스레드 2는 A와 B가 동시에 있어야만 스레드 1에 신호를 보낼 수 있다.결과적으로 한 라인은 신호를 기다리고 다른 라인은 자물쇠를 풀기를 기다리고 있다.
    차이점은 다음과 같습니다.
  • 사라진 자물쇠에서 두 라인이 서로 자물쇠를 풀기를 기다린다.
  • 플러그 튜브의 스레드 잠금 중 스레드 1은 잠금 A를 가지고 스레드 2의 신호를 기다린다.스레드 2는 스레드 1에 신호를 보내기 위해 A 를 잠가야 한다.

  • 에피소드를 번역하다.
    원문: At first glance this implementation may look fine, but notice how the lock() method calls queue Object.wait(); from inside two synchronized blocks.
    설명: "notice how"는 "xxx가 어떻게 되는지 주의"로 쉽게 번역할 수 있지만, 사실은 그렇지 않다.이곳의'how'는'어떻게'가 아니라 안내어이며 목적어 종문을 이어받는다.그러니까 직역할 필요 없어.
    번역문: 언뜻 보기에는 이 실현이 괜찮은 것 같지만, lock () 방법은queueObject를 호출했습니다.wait (), 이 호출은 두 동기화 블록 내부에 있습니다.
    다음: 2020 버전 자바 병렬 및 다중 스레드 튜토리얼 (20): 슬라이딩 조건
    동시 사용 칼럼: Java 동시 사용 및 멀티스레드 자습서 2020 버전

    좋은 웹페이지 즐겨찾기