java 다중 스레드 - 자물쇠 상세 및 예시 코드

자바 5부터, 자바.util.concurrent.locks 패키지에는 자물쇠의 실현이 포함되어 있기 때문에 자물쇠를 실현할 필요가 없습니다.하지만 자물쇠를 어떻게 사용하는지 알아야 한다.
간단한 자물쇠 하나
자바의 동기화 블록부터 시작합니다.

public class Counter{
  private int count = 0;

  public int inc(){
    synchronized(this){
      return ++count;
    }
  }
}

inc () 방법에synchronized (this) 코드 블록이 있는 것을 볼 수 있습니다.이 코드 블록은 같은 시간에 하나의 라인만 return++ count를 실행할 수 있도록 보장합니다.synchronized의 동기화 블록에 있는 코드는 더욱 복잡할 수 있지만++count라는 간단한 조작은 라인 동기화의 의미를 충분히 나타낼 수 있다.
다음 Counter 클래스는 synchronized 대신 Lock으로 동일한 목적을 달성합니다.

public class Counter{
  private Lock lock = new Lock();
  private int count = 0;

  public int inc(){
    lock.lock();
    int newCount = ++count;
    lock.unlock();
    return newCount;
  }
}

lock () 방법은 Lock 실례 대상을 잠그기 때문에 이 대상에 lock () 방법을 호출하는 모든 루트가 이 Lock 대상의 unlock () 방법이 호출될 때까지 막힙니다.
여기에는 Lock 클래스의 간단한 구현이 있습니다.

public class Counter{
public class Lock{
  private boolean isLocked = false;

  public synchronized void lock()
    throws InterruptedException{
    while(isLocked){
      wait();
    }
    isLocked = true;
  }

  public synchronized void unlock(){
    isLocked = false;
    notify();
  }
}

그 중의 while (isLocked) 순환을 주의하십시오. 이것은'자전 자물쇠'라고도 합니다.isLocked가true일 때, lock () 를 호출하는 라인은wait () 호출에서 대기를 막습니다.이 스레드가 notify () 호출을 받지 못하거나wait () 에서 되돌아오는 것을 방지하기 위해서, 이 스레드는 isLocked 조건을 다시 검사해서 현재 안전하게 계속 실행할 수 있는지 아니면 다시 기다려야 하는지 결정합니다. 스레드가 깨어났다고 해서 안전하게 계속 실행할 수 있다고 생각하지 않습니다.만약 isLocked가false라면, 현재 라인은while (isLocked) 순환을 종료하고, isLocked를true로 설정하여 lock () 방법을 호출하고 있는 다른 라인이 Lock 실례에 잠길 수 있도록 합니다.
루틴이 임계구 (lock () 와 unlock () 사이에 있는 코드를 완성하면 unlock () 를 호출합니다.unlock () 을 실행하면 isLocked 를false 로 다시 설정하고, 그 중 하나 (있으면) 를 lock () 방법에서wait () 함수를 호출하여 대기 중인 라인에 알립니다.
자물쇠의 재입성
Java의 synchronized 동기화 블록은 다시 들어갈 수 있습니다.이것은 만약에 하나의 자바 라인이 코드의synchronized 동기화 블록에 들어갔고 이 동기화 블록이 사용하는 동기화 대상에 대응하는 파이프의 자물쇠를 얻었다면 이 라인은 같은 파이프 대상이 동기화하는 다른 자바 코드 블록에 들어갈 수 있음을 의미한다.다음은 예입니다.

public class Reentrant{
  public synchronized outer(){
    inner();
  }

  public synchronized inner(){
    //do something
  }
}

outer () 와 inner () 는 모두synchronized로 선언됩니다. 이것은 자바에서synchronized (this) 블록과 같은 효과입니다.만약 한 라인이 outer () 를 호출했다면, outer () 에서 inner () 를 호출하는 것은 아무런 문제가 없습니다. 왜냐하면 이 두 가지 방법 (코드 블록) 은 모두 같은 파이프라인 대상 ("this") 에 의해 동기화되기 때문입니다.만약 하나의 스레드가 하나의 스레드 대상의 자물쇠를 가지고 있다면, 이 스레드 대상에 동기화된 모든 코드 블록에 접근할 권리가 있습니다.이것이 바로 다시 들어갈 수 있는 것이다.라인은 이미 가지고 있는 자물쇠와 동기화된 코드 블록에 들어갈 수 있습니다.
앞에서 제시한 자물쇠는 다시 들어갈 수 있는 것이 아니다.만약 우리가 아래와 같이 Reentrant 클래스를 다시 쓴다면, 루트가 outer () 를 호출할 때, inner () 방법의 lock에 있습니다.lock () 에서 막습니다.

public class Reentrant2{
  Lock lock = new Lock();

  public outer(){
    lock.lock();
    inner();
    lock.unlock();
  }

  public synchronized inner(){
    lock.lock();
    //do something
    lock.unlock();
  }
}

outer () 를 호출하는 루틴은 먼저 Lock 실례를 잠그고 inner () 를 계속 호출합니다.inner () 방법에서 이 루틴은 다시 한 번 Lock 실례를 잠그려고 시도합니다. 결과적으로 이 동작은 실패할 것입니다. (즉, 이 루틴은 막힐 것입니다.) 왜냐하면 이 루틴 실례는 outer () 방법에서 잠겼기 때문입니다.
두 번 lock () 사이에 unlock () 를 호출하지 않으면 두 번째 lock () 호출이 막힙니다. lock () 가 실현되는 것을 보면 그 원인이 뚜렷합니다.

public class Lock{
  boolean isLocked = false;

  public synchronized void lock()
    throws InterruptedException{
    while(isLocked){
      wait();
    }
    isLocked = true;
  }

  ...
}

lock () 에서 로그아웃할 수 있는지 여부는 while 순환 (자전 자물쇠) 의 조건에 의해 결정됩니다.현재의 판단 조건은 isLocked가false일 때만 lock 작업이 허용되고, 어느 라인이 그것을 잠갔는지 고려하지 않는 것이다.
이 Lock 클래스를 재입력할 수 있도록 약간의 변경이 필요합니다.

public class Lock{
  boolean isLocked = false;
  Thread lockedBy = null;
  int lockedCount = 0;

  public synchronized void lock()
    throws InterruptedException{
    Thread callingThread =
      Thread.currentThread();
    while(isLocked && lockedBy != callingThread){
      wait();
    }
    isLocked = true;
    lockedCount++;
    lockedBy = callingThread;
 }

  public synchronized void unlock(){
    if(Thread.curentThread() ==
      this.lockedBy){
      lockedCount--;

      if(lockedCount == 0){
        isLocked = false;
        notify();
      }
    }
  }

  ...
}

현재while 순환 (자전 자물쇠) 도 이 자물쇠의 실례를 잠근 라인을 고려했습니다.현재 자물쇠 대상이 자물쇠를 채우지 않았거나 (isLocked=false) 현재 호출 스레드가 이 자물쇠의 실례에 자물쇠를 넣었다면while 순환이 실행되지 않고 lock () 를 호출하는 스레드가 이 방법을 종료할 수 있습니다.
그 밖에 우리는 같은 라인에서 자물쇠 대상에 대한 자물쇠를 반복하는 횟수를 기록해야 한다.그렇지 않으면, 현재 자물쇠가 여러 번 잠겼더라도 unblock () 호출을 한 번 하면 전체 자물쇠가 해제됩니다.unlock () 호출이 lock () 호출에 대응하는 횟수에 도달하기 전에, 우리는 자물쇠가 해제되는 것을 원하지 않습니다.
이제 이 Lock 클래스는 다시 들어갈 수 있습니다.
자물쇠의 공평성
Java의synchronized 블록은 그들의 라인에 들어가려는 순서를 보장하지 않습니다.따라서 만약에 여러 개의 라인이 같은 synchronized 동기화 블록을 계속 경쟁적으로 방문한다면 위험이 존재한다. 그 중 하나 또는 여러 개의 라인은 영원히 방문권을 얻지 못한다. 즉, 방문권은 항상 다른 라인에 분배된다는 것이다.이런 상황을 라인 기아라고 부른다.이런 문제를 피하기 위해서는 자물쇠가 공평성을 실현해야 한다.본고에서 보여준 자물쇠는 내부에synchronized 동기화 블록으로 이루어져 있기 때문에 공평성을 보장하지 않는다.
finally 문장에서 unlock () 을 호출합니다
만약 Lock으로 임계 구역을 보호하고 임계 구역에 이상이 발생할 수 있다면,finally 문장에서 unlock () 를 호출하는 것이 매우 중요하다.이렇게 하면 이 자물쇠 대상이 다른 라인이 계속 자물쇠를 채울 수 있도록 해제될 수 있음을 보장할 수 있다.다음은 예입니다.

lock.lock();
try{
  //do critical section code,
  //which may throw exception
} finally {
  lock.unlock();
}
이 간단한 구조는 임계구가 이상을 던졌을 때 Lock 대상이 잠금 해제될 수 있도록 보장합니다.finally 문장에서 호출된 unlock () 이 아니라면, 임계 구역에서 이상이 발생하면, Lock 대상은 영원히 잠긴 상태에 머무르게 되며, 이로 인해 이 Lock 대상에서 lock () 를 호출하는 다른 모든 라인이 계속 막힐 수 있습니다.
이상은 자바 다중 스레드 자물쇠에 대한 자료 정리입니다. 후속적으로 관련 자료를 계속 보충합니다. 본 사이트에 대한 지지에 감사드립니다!

좋은 웹페이지 즐겨찾기