Java 멀티스레드 동기화 학습

만약 당신의 자바 기초가 약하거나 자바 다중 라인을 잘 모른다면 먼저 이 글을 보십시오Java 멀티스레드의 스레드 정의, 상태 및 속성 학습
동기화는 자바 다중 루틴의 난점으로 우리가android 개발을 할 때도 거의 적용되지 않았지만, 이것은 우리가 동기화에 익숙하지 않은 이유가 아니다.이 글이 더 많은 사람들이 자바의 동기화를 이해하고 응용할 수 있기를 바랍니다.
다중 스레드의 응용에서 두 개 또는 두 개 이상의 스레드는 같은 데이터에 대한 접근을 공유해야 한다.만약 두 라인이 같은 대상을 액세스하고 모든 라인이 이 대상을 수정하는 방법을 사용한다면, 이런 상황은 통상적으로 경쟁 조건이 된다.
경쟁 조건에서 가장 이해하기 쉬운 예는 바로 기차표를 파는 것이다. 기차표는 일정한 것이지만 기차표를 파는 창구는 곳곳에 있다. 창구마다 하나의 노선에 해당한다. 이렇게 많은 노선은 모든 기차표라는 자원을 함께 사용한다.또한 원자성을 보장할 수 없다. 만약에 한 시간에 두 개의 노선이 이 자원을 동시에 사용한다면 그들이 꺼낸 기차표는 똑같다(좌석번호가 같다). 그러면 승객들에게 폐를 끼칠 수 있다.해결 방법은 한 노선이 기차표라는 자원을 사용하려고 할 때, 우리는 그것에게 자물쇠를 건네주고, 일이 끝난 후에 이 자원을 사용할 다른 노선에 자물쇠를 건네주는 것이다.이렇게 하면 상술한 상황이 나타나지 않을 것이다.
1. 객체 잠금
synchronized 키워드는 자동으로 자물쇠와 관련된 조건을 제공합니다. 대부분의 현식 자물쇠가 필요한 경우synchronized를 사용하면 매우 편리하지만, ReentrantLock 클래스와 조건 대상을 이해할 때synchronized 키워드를 더 잘 이해할 수 있습니다.ReentrantLock은 JAVA SE 5.0에서 도입한 것으로, ReentrantLock으로 코드 블록을 보호하는 구조는 다음과 같습니다.

mLock.lock();
try{
...
}
finally{
mLock.unlock();
}

이 구조는 언제든지 한 라인만 임계 구역에 들어갈 수 있도록 합니다. 한 라인이 자물쇠 대상을 봉쇄하면 다른 모든 라인은lock 문장을 통과할 수 없습니다.다른 스레드가 lock을 호출할 때, 첫 번째 스레드가 잠금 대상을 방출할 때까지 막힙니다.자물쇠 해제 작업을finally에 놓는 것은 매우 필요한 것이다. 만약 임계 구역에 이상이 발생하면 자물쇠는 반드시 방출해야 한다. 그렇지 않으면 다른 라인은 영원히 막힐 것이다.
2. 조건 객체
임계구에 들어갔을 때, 어떤 조건이 충족된 후에야 그것을 집행할 수 있다는 것을 발견하였다.하나의 조건 대상을 사용하여 이미 자물쇠를 얻었지만 유용한 작업을 할 수 없는 라인을 관리해야 한다. 조건 대상은 조건 변수라고도 부른다.
다음은 왜 조건의 대상이 필요한지 예를 보도록 하겠습니다.
만약에 한 장면을 가정하면 우리는 은행으로 이체해야 한다. 우리는 먼저 은행의 종류를 썼는데 그 구조 함수는 계좌 수량과 계좌 금액을 입력해야 한다

public class Bank {
private double[] accounts;
  private Lock bankLock;
  public Bank(int n,double initialBalance){
    accounts=new double[n];
    bankLock=new ReentrantLock();
    for (int i=0;i<accounts.length;i++){
      accounts[i]=initialBalance;
    }
  }
  }

다음에 우리는 돈을 인출하고 인출하는 방법을 써야 한다. from은 이체자이고 to는 수신자이며amount 이체 금액이다. 그 결과 우리는 이체자의 잔액이 부족하다는 것을 발견했다. 만약에 다른 라인이 이 이 이체자에게 충분한 돈을 저축하면 이체에 성공할 수 있다. 그러나 이 라인은 이미 자물쇠를 얻었다. 이것은 배타성을 가지고 다른 라인도 자물쇠를 가져와 예금 조작을 할 수 없다.이것이 바로 우리가 조건 대상을 도입해야 하는 이유다.

  public void transfer(int from,int to,int amount){
    bankLock.lock();
    try{
      while (accounts[from]<amount){
        //wait
      }
    }finally {
      bankLock.unlock();
    }
  }
하나의 자물쇠 대상은 여러 개의 관련 조건 대상을 가지고 있다. newCondition 방법으로 조건 대상을 얻을 수 있다. 우리는 조건 대상을 얻은 후await 방법을 호출하면 현재 라인이 막히고 자물쇠를 포기한다

public class Bank {
private double[] accounts;
  private Lock bankLock;
  private Condition condition;
  public Bank(int n,double initialBalance){
    accounts=new double[n];
    bankLock=new ReentrantLock();
    // 
    condition=bankLock.newCondition();
    for (int i=0;i<accounts.length;i++){
      accounts[i]=initialBalance;
    }
  }
  public void transfer(int from,int to,int amount) throws InterruptedException {
    bankLock.lock();
    try{
      while (accounts[from]<amount){
        // , 
        condition.await();
      }
    }finally {
      bankLock.unlock();
    }
  }
}
자물쇠를 얻기 위한 루트와 await 방법을 호출하는 루트는 본질적으로 다르다. 일단 하나의 루트가 호출되는 await 방법은 이 조건의 대기 집합에 들어간다.자물쇠를 사용할 수 있을 때, 이 라인은 바로 자물쇠를 풀 수 없고, 반대로 그는 다른 라인이 같은 조건의signalAll 방법을 호출할 때까지 막힌 상태에 있다.다른 라인이 이전의 이체자에게 이체를 준비할 때,condition만 호출합니다.signalAll();이 호출은 이 조건 때문에 기다리는 모든 라인을 다시 활성화합니다.
한 라인이await방법을 호출하면 그는 자신을 다시 활성화할 수 없고, 다른 라인에서signalAll방법을 호출하여 자신을 활성화할 수 있기를 희망한다. 만약 다른 라인이 기다리는 라인을 활성화하지 않으면 사쇄현상이 발생한다. 만약에 모든 다른 라인이 막히면 마지막 활동 라인이 다른 라인의 막힌 상태를 해제하기 전에await를 호출하면 그것도 막힌다.다른 라인의 막힘을 해소할 수 있는 어떤 라인도 없어서 프로그램이 끊겼다.
그럼 시그널All을 언제 호출합니까?정상적으로 말하자면 라인의 방향이 바뀌기를 기다리는 데 도움이 될 때signal All을 호출해야 한다.이 예에서 계좌 잔액이 변화할 때 기다리는 라인은 잔액을 검사할 기회가 있어야 한다는 것이다.

 public void transfer(int from,int to,int amount) throws InterruptedException {
    bankLock.lock();
    try{
      while (accounts[from]<amount){
        // , 
        condition.await();
      }
      // 
      ...
      condition.signalAll();
    }finally {
      bankLock.unlock();
    }
  }

signalAll 방법을 호출할 때 대기 라인을 즉시 활성화하는 것은 아닙니다. 대기 라인의 막힘만 없애고 현재 라인이 동기화 방법을 종료한 후에 경쟁을 통해 대상에 대한 접근을 실현할 수 있도록 합니다.또 하나의 방법은signal이다. 이것은 임의로 어떤 라인의 막힘을 해제하는 것이다. 만약에 이 라인이 여전히 운행할 수 없다면 다시 막힌다. 다른 라인이signal을 다시 호출하지 않으면 시스템은 사라진다.
3. 동기화된 키워드
Lock과Condition 인터페이스는 프로그래머에게 높은 잠금 제어를 제공하지만 대부분의 경우 그런 제어가 필요하지 않으며 자바 언어 내부에 삽입된 메커니즘을 사용할 수 있다.Java 1.0 버전부터 Java의 각 객체에는 내부 잠금이 있습니다.만약 한 방법이synchronized 키워드로 성명한다면, 대상의 자물쇠는 모든 방법을 보호할 것입니다.즉, 이 방법을 사용하려면 라인이 내부의 대상 자물쇠를 얻어야 한다는 것이다.
다시 말하면,

public synchronized void method(){

}
... 과 같다

public void method(){
this.lock.lock();
try{

}finally{
this.lock.unlock();
}
상기 은행의 예에서 우리는 은행류의transfer 방법을synchronized로 표시할 수 있으며, 표시된 자물쇠를 사용하지 않습니다.
내부 대상 자물쇠는 관련 조건이 하나뿐입니다.wait 확대는 하나의 라인에 추가되어 대기 집중에 추가됩니다.notifyAll 또는 notify 방법은 대기 라인의 막힘 상태를 해제합니다.즉,wait는 호출 조건에 해당한다는 것이다.await(), notifyAll은condition과 같습니다.signalAll();
우리 위의 예transfer 방법도 이렇게 쓸 수 있다.

  public synchronized void transfer(int from,int to,int amount)throws InterruptedException{
    while (accounts[from]<amount) {
      wait();
    }
    // 
    ...
    notifyAll();  
    }

synchronized 키워드를 사용하여 코드를 작성하는 것은 매우 간결해야 한다는 것을 알 수 있습니다. 물론 이 코드를 이해해야 합니다. 모든 대상에 내부 자물쇠가 있고 내부 조건이 있다는 것을 알아야 합니다.자물쇠로synchronized 방법에 들어가려는 라인을 관리하고, 조건으로wait를 호출하는 라인을 관리합니다.
4. 동기화 차단
위에서 우리가 말한 바와 같이 모든 자바 대상은 하나의 자물쇠가 있는데 라인은 동기화 방법을 호출하여 자물쇠를 얻을 수 있고 또 다른 메커니즘은 자물쇠를 얻을 수 있다. 하나의 동기화 차단에 들어가면 라인이 다음과 같은 형식의 차단에 들어간다.

synchronized(obj){

}
그래서 그는obj의 자물쇠를 얻었다.뱅크 클래스 다시 볼게요.

public class Bank {
private double[] accounts;
private Object lock=new Object();
  public Bank(int n,double initialBalance){
    accounts=new double[n];
    for (int i=0;i<accounts.length;i++){
      accounts[i]=initialBalance;
    }
  }
  public void transfer(int from,int to,int amount){
    synchronized(lock){
     // 
      ...
    }
  }
}
여기서 lock 객체는 각 Java 객체가 가지고 있는 자물쇠를 사용하는 데 사용됩니다.때때로 개발자는 하나의 대상의 자물쇠를 사용하여 추가 원자 조작을 실현하는데 이를 클라이언트 자물쇠라고 부른다.예를 들어 Vector 클래스는 동기화됩니다.이제 Vector에 은행 잔액을 저장한다고 가정하십시오.

 public void transfer(Vector<Double>accounts,int from,int to,int amount){
 accounts.set(from,accounts.get(from)-amount);
 accounts.set(to,accounts.get(to)+amount;
}

Vecror 클래스의 get과 set 방법은 동기화되지만, 이것은 우리에게 도움이 되지 않습니다.첫 번째 get에 대한 호출이 끝난 후, 한 라인은transfer 방법에서 운행권을 박탈당할 수 있으며, 다른 라인은 같은 저장 위치에 다른 값을 저장할 수 있지만, 우리는 이 자물쇠를 캡처할 수 있다

 public void transfer(Vector<Double>accounts,int from,int to,int amount){
 synchronized(accounts){
 accounts.set(from,accounts.get(from)-amount);
 accounts.set(to,accounts.get(to)+amount;
 }
}

클라이언트 잠금 (동기화 코드 블록) 은 매우 취약합니다. 일반적으로 사용하는 것을 추천하지 않습니다. 일반적으로 동기화를 실현하려면 자바를 사용하는 것이 가장 좋습니다.util.concurrent 패키지에서 제공하는 클래스, 예를 들어 대기열을 막습니다.만약 동기화 방법이 당신의 프로그램에 적합하다면, 가능한 한 동기화 방법을 사용하십시오. 그는 코드를 작성하는 수량을 줄이고 오류 확률을 줄일 수 있습니다. 만약에 특별히 Lock/Condition 구조가 제공하는 독특한 특성을 사용해야 Lock/Condition을 사용할 수 있습니다.
이상은 본문의 전체 내용입니다. 여러분의 학습에 도움이 되기를 바랍니다.

좋은 웹페이지 즐겨찾기