Java 다중 스레드 - 스레드의 동기화 및 잠금 문제

1. 동기화 문제 제기
스레드의 동기화는 여러 스레드가 하나의 데이터 대상에 접근할 때 데이터에 대한 파괴를 방지하기 위한 것이다.
예를 들어 두 스레드 ThreadA, ThreadB 모두 같은 대상인 Foo 대상을 조작하고 Foo 대상의 데이터를 수정합니다.

package cn.thread;

public class Foo {
  private int x = 100;

  public int getX() {
    return x;
  }

  public int fix(int y) {
    x = x - y;
    return x;
  }
}


package cn.thread;

public class MyRunnable implements Runnable {
  private Foo foo = new Foo();

  public static void main(String[] args) {
    MyRunnable run = new MyRunnable();
    Thread ta = new Thread(run, "Thread-A");
    Thread tb = new Thread(run, "Thread-B");
    ta.start();
    tb.start();
  }

  public void run() {
    for (int i = 0; i < 3; i++) {
      this.fix(30);
      try {
        Thread.sleep(1);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName() + " :  foo x = " + foo.getX());
    }
  }

  public int fix(int y) {
    return foo.fix(y);
  }

}

실행 결과:
Thread-B: 현재 foo 객체의 x 값 = 40
Thread-A: 현재 foo 객체의 x 값 = 40
Thread-B: 현재 foo 객체의 x 값 = -20
Thread-A: 현재 foo 객체의 x 값 = -20
Thread-B: 현재 foo 객체의 x 값 = -80
Thread-A: 현재 foo 객체의 x 값 = -80
결과적으로 이러한 수출 값은 분명히 불합리하다는 것을 발견하였다.두 스레드가 제어되지 않고 Foo 객체에 액세스하고 데이터를 수정하기 때문입니다.
결과의 합리성을 유지하려면 한 가지 목적을 달성해야 한다. Foo에 대한 접근을 제한하는 것이다. 매번 한 라인만 접근할 수 있다.이렇게 하면 Foo 객체에서 데이터의 합리성을 보장할 수 있습니다.
구체적인 Java 코드에서 두 가지 작업을 수행해야 합니다.
경쟁적으로 접근하는 자원 클래스 Foo 변수 x를private로 표시합니다.
변수를 수정하는 코드를 동기화하려면synchronized 키워드 동기화 방법이나 코드를 사용하십시오.

package cn.thread;

public class Foo2 {
  private int x = 100;

  public int getX() {
    return x;
  }

  // 
  public synchronized int fix(int y) {
    x = x - y;
    System.out.println(" "+Thread.currentThread().getName() + " , “" + y
        + "”, :" + x);
    return x;
  }
  
//  // 
//  public int fix(int y) {
//    synchronized (this) {
//      x = x - y;
//      System.out.println(" "+Thread.currentThread().getName() + " , “" + y
//          + "”, :" + x);
//    }
//    
//    return x;
//  }

}

package cn.thread;

public class MyRunnable2 {

  public static void main(String[] args) {
    MyRunnable2 run = new MyRunnable2();
    Foo2 foo2=new Foo2();
    
    MyThread t1 = run.new MyThread(" A", foo2, 10);
    MyThread t2 = run.new MyThread(" B", foo2, -2);
    MyThread t3 = run.new MyThread(" C", foo2, -3);
    MyThread t4 = run.new MyThread(" D", foo2, 5);
    
    t1.start();
    t2.start();
    t3.start();
    t4.start();
  }
  
  class MyThread extends Thread {
    private Foo2 foo2;
    /** */
    private int y = 0;
    
    MyThread(String name, Foo2 foo2, int y) {
      super(name);
      this.foo2 = foo2;
      this.y = y;
    }

    public void run() {
      foo2.fix(y);
    }
  }

}

스레드 A 실행 종료, "10"감소, 현재값: 90
스레드 C 실행 종료, "-3"감소, 현재값: 93
스레드 B 실행 종료, "-2"감소, 현재값: 95
스레드 D 실행 종료, "5"감소, 현재값: 90
2. 동기화 및 잠금
1. 자물쇠의 원리
Java의 각 객체에는 내장 잠금이 있습니다.
프로그램이 비정상적인synchronized 동기화 방법으로 실행될 때, 실행 중인 코드 클래스의 현재 실례 (this 실례) 와 관련된 자물쇠를 자동으로 가져옵니다.하나의 대상을 얻는 자물쇠는 자물쇠를 가져오거나 잠그거나 대상에 잠그거나 동기화라고도 한다.
프로그램이 synchronized 동기화 방법이나 코드 블록에 실행될 때 이 대상 자물쇠가 작동합니다.
하나의 대상은 하나의 자물쇠만 있다.따라서 만약 한 라인이 이 자물쇠를 얻게 된다면, 첫 번째 라인이 자물쇠를 풀거나 되돌아올 때까지 다른 라인이 자물쇠를 얻을 수 없다.이것은 자물쇠가 풀릴 때까지 그 대상의synchronized 방법이나 코드 블록에 들어갈 수 없다는 것을 의미한다.
자물쇠를 놓는 것은 자물쇠 라인이synchronized 동기화 방법이나 코드 블록을 종료하는 것을 가리킨다.
자물쇠와 동기화에 관한 몇 가지 요점이 있다.
1), 동기화 방법만 있을 뿐 변수와 클래스는 동기화할 수 없다.
2), 각 객체는 하나의 자물쇠만 있습니다.동기화를 언급할 때, 무엇을 동기화해야 합니까?즉, 어느 대상에 동기화됩니까?
3), 클래스의 모든 방법을 동기화할 필요가 없다. 클래스는 동기화와 비동기화 방법을 동시에 가질 수 있다.
4), 만약에 두 라인이 한 클래스의synchronized 방법을 실행하고 두 라인이 같은 실례를 사용하여 방법을 호출하려면 한 번에 한 라인만 실행할 수 있고 다른 한 라인은 자물쇠가 풀릴 때까지 기다려야 한다.즉, 만약에 하나의 라인이 대상에 자물쇠를 얻게 된다면, 다른 라인이 (이 대상의) 클래스에 들어갈 수 있는 동기화 방법이 없다.
5), 만약 스레드가 동기화 및 비동기화 방법을 가지고 있다면, 비동기화 방법은 여러 스레드에 자유롭게 접근할 수 있고 자물쇠의 제한을 받지 않을 수 있다.
6), 루틴이 수면을 취할 때, 루틴이 가지고 있는 어떤 자물쇠도 방출되지 않는다.
7), 스레드는 여러 개의 자물쇠를 얻을 수 있다.예를 들어 한 대상의 동기화 방법에서 다른 대상의 동기화 방법을 호출하면 두 대상의 동기화 자물쇠를 얻는다.
8), 동기화 손해의 병발성은 가능한 한 동기화 범위를 줄여야 한다.동기화는 전체 방법을 동기화할 수 있을 뿐만 아니라, 동기화 방법 중의 일부 코드 블록도 동기화할 수 있다.
9), 동기화 코드 블록을 사용할 때 어느 대상에 동기화할지, 즉 어떤 대상의 자물쇠를 가져올지 지정해야 한다.예:

public int fix(int y) {
   synchronized (this) {
      x = x - y;
   }
   return x;
}
물론 동기화 방법도 비동기화 방법으로 바꿀 수 있지만 기능은 완전히 같다. 예를 들어

public synchronized int getX() {
   return x++;
}
... 과

public int getX() {
   synchronized (this) {
      return x++;
   }
}
효과는 완전히 같다.
3. 정적 방법 동기화
정적 방법을 동기화하려면 전체 클래스 대상에 사용할 자물쇠가 필요합니다. 이 대상이 바로 이 클래스 (XXX.class) 입니다.
예:

public static synchronized int setName(String name){
   Xxx.name = name;
}
... 과 같다

public static int setName(String name){
   synchronized(Xxx.class){
      Xxx.name = name;
   }
}
4. 만약 라인이 자물쇠를 얻지 못하면 어떻게 될까
만약 스레드가 동기화 방법에 들어가려고 시도했지만, 자물쇠가 점용되었다면, 스레드는 이 대상에 막힌다.실질적으로, 라인이 이 대상의 연못에 들어가면, 자물쇠가 풀리고, 이 라인이 다시 실행 가능하거나 실행될 때까지 어디에서 기다려야 한다.
차단을 고려할 때 잠금에 사용되는 객체에 유의해야 합니다.
1. 같은 대상의 비정적 동기화 방법을 호출하면 서로 막힌다.만약 서로 다른 대상이라면, 모든 라인은 자신의 대상의 자물쇠가 있고, 라인 간에는 서로 관여하지 않는다.
2. 같은 클래스의 정적 동기화 방법을 호출하는 라인은 서로 막힌다. 모두 같은 클래스 대상에 잠겨 있다.
3. 정적 동기화 방법과 비정적 동기화 방법은 영원히 서로 막히지 않을 것이다. 왜냐하면 정적 방법은 Class 대상에 잠겨 있고 비정적 방법은 이 종류의 대상에 잠겨 있기 때문이다.
4. 동기화 코드 블록에 대해 잠금 (synchronized 뒤쪽 괄호의 내용) 에 사용된 대상을 똑똑히 보아야 한다.같은 대상에서 동기화하는 라인은 서로 막히고, 서로 다른 대상에 잠긴 라인은 영원히 서로 막히지 않는다.
5. 동기화가 필요한 경우
여러 개의 스레드가 서로 배타적(교환 가능) 데이터에 동시에 접근할 때, 두 스레드가 동시에 변경되지 않도록 동기화해야 한다.
비정적 필드에서 변경할 수 있는 데이터는 일반적으로 비정적 방법으로 접근합니다.
정적 필드에서 변경할 수 있는 데이터는 일반적으로 정적 방법으로 접근합니다.
만약 비정적 방법에서 정적 필드를 사용하거나 정적 필드에서 비정적 방법을 사용해야 한다면 문제는 매우 복잡해질 것이다.SJCP 시험 범위를 넘어섰습니다.
6. 스레드 보안 클래스
하나의 클래스가 데이터를 보호하기 위해 동기화되었을 때, 이 클래스는'스레드 안전'이라고 부른다.
설령 스레드 안전 클래스라고 하더라도 특별히 조심해야 한다. 왜냐하면 조작된 스레드가 사이라고 해서 여전히 안전하지 않기 때문이다.
7. 스레드 동기화 소결
1. 스레드 동기화의 목적은 여러 개의 스레드가 한 자원에 접근할 때 자원에 대한 파괴를 보호하기 위한 것이다.
2. 스레드 동기화 방법은 자물쇠를 통해 이루어진다. 모든 대상은 자물쇠가 하나밖에 없다. 이 자물쇠는 특정한 대상과 관련이 있다. 스레드가 대상 자물쇠를 가져오면 다른 대상에 접근하는 스레드는 그 대상의 다른 동기화 방법에 접근할 수 없다.
3. 정적 동기화 방법에 대해 자물쇠는 이 종류를 대상으로 하고 자물쇠 대상은 이 종류의 Class 대상이다.정태와 비정태 방법의 자물쇠는 서로 관여하지 않는다.하나의 라인은 자물쇠를 얻고, 하나의 동기화 방법에서 다른 대상의 동기화 방법에 접근할 때, 이 두 개의 대상 자물쇠를 얻는다.
4. 동기화에 대해 항상 어느 대상에 동기화를 해야 하는지가 관건이다.
5. 스레드 안전 클래스를 작성하려면 여러 개의 스레드 경쟁 방문 자원의 논리와 안전에 대해 정확한 판단을 하고'원자'조작에 대해 분석을 하며 원자 조작 기간에 다른 스레드가 경쟁 자원에 접근할 수 없도록 해야 한다.
6. 여러 개의 라인이 대상 자물쇠를 기다리는 동안 자물쇠를 얻지 못한 라인이 막힙니다.
7. 사쇄는 라인 간에 서로 자물쇠를 기다리기 때문에 실제적으로 발생할 확률이 매우 적다.정말 너에게 자물쇠 잠금 프로그램을 쓰라고 했는데, 꼭 잘 되는 것은 아니다. 허허.그러나 프로그램에 자물쇠가 사라지면 프로그램은 죽는다.
원문 링크:http://www.cnblogs.com/linjiqin/p/3208843.html
이상은 본문의 전체 내용입니다. 여러분의 학습에 도움이 되고 저희를 많이 응원해 주십시오.

좋은 웹페이지 즐겨찾기