javasynchronized 키워드의 사용법

5939 단어 javasynchronized
0. 선도적인 문제 코드
아래의 코드는 하나의 계수기, 두 개의 라인이 동시에 i에 대해 누적된 조작을 하고 각각 1000000번을 실행하는 것을 보여 준다.우리가 기대하는 결과는 틀림없이 i=200000이다.그러나 우리가 여러 번 실행한 후에 i의 값이 영원히 2000000보다 작다는 것을 발견할 수 있다.이것은 두 개의 스레드가 동시에 i에 기록될 때, 그 중의 한 스레드의 결과가 다른 스레드를 덮어쓰기 때문이다.

public class AccountingSync implements Runnable {
  static int i = 0;
  public void increase() {
    i++;
  }
 
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }
 
  public static void main(String[] args) throws InterruptedException {
    AccountingSync accountingSync = new AccountingSync();
 
    Thread t1 = new Thread(accountingSync);
    Thread t2 = new Thread(accountingSync);
 
    t1.start();
    t2.start();
 
    t1.join();
    t2.join();
 
    System.out.println(i);
  }
}
근본적으로 이 문제를 해결하려면, 우리는 반드시 여러 개의 스레드가 i를 조작할 때, 완전한 동기화를 보장해야 한다.즉, A라인이 i에 대해 쓸 때 B라인은 쓸 수 없을 뿐만 아니라 읽을 수도 없다.
1.synchronized 키워드의 역할
키워드synchronized의 작용은 사실 라인 간의 동기화를 실현하는 것이다.그것의 작업은 동기화된 코드를 잠그고 매번 하나의 라인만 동기화 블록에 들어가서 라인 간의 안전성을 확보하는 것이다.위의 코드에서 i++의 조작은 동시에 또 하나의 라인만 실행할 수 있다.
2. synchronized 키워드의 사용법
대상 잠금 지정: 주어진 대상을 잠그고 동기화 코드 블록에 들어가 주어진 대상의 잠금을 얻으려면
실례 방법에 직접적으로 작용: 현재 실례에 자물쇠를 채우는 것과 같습니다. 동기화 코드 블록에 들어가면 현재 실례의 자물쇠를 얻을 수 있습니다. (이것은 Thread를 만들 때 같은 Runnable의 실례를 사용해야 합니다.)
정적 방법에 직접 작용: 현재 클래스에 자물쇠를 채우는 것과 같고, 동기화 코드 블록에 들어가기 전에 현재 클래스의 자물쇠를 얻는 것과 같다
2.1 객체 잠금 지정
다음 코드는synchronized를 주어진 대상에 작용합니다.여기에 주의하는 것이 하나 있다. 주어진 대상은 반드시 static적이어야 한다. 그렇지 않으면 우리는 매번 new의 라인이 나올 때마다 서로 이 대상을 공유하지 않고 자물쇠를 채우는 의미도 존재하지 않는다.

public class AccountingSync implements Runnable {
  final static Object OBJECT = new Object();
 
  static int i = 0;
  public void increase() {
    i++;
  }
 
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      synchronized (OBJECT) {
        increase();
      }
    }
  }
 
  public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new AccountingSync());
    Thread t2 = new Thread(new AccountingSync());
 
    t1.start();
    t2.start();
 
    t1.join();
    t2.join();
 
    System.out.println(i);
  }
}
2.2 실례적인 방법에 직접 작용
synchronized 키워드는 실례 방법에 작용합니다. 즉, increase () 방법에 들어가기 전에 현재 실례의 자물쇠를 얻어야 합니다.이것은 우리가thread 실례를 만들 때 같은 Runnable 대상 실례를 사용하도록 요구합니다.그렇지 않으면, 라인의 자물쇠는 모두 같은 실례 위에 있지 않아서, 자물쇠/동기화 문제를 이야기할 방법이 없다.

public class AccountingSync implements Runnable {
  static int i = 0;
  public synchronized void increase() {
    i++;
  }
 
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }
 
  public static void main(String[] args) throws InterruptedException {
    AccountingSync accountingSync = new AccountingSync();
 
    Thread t1 = new Thread(accountingSync);
    Thread t2 = new Thread(accountingSync);
 
    t1.start();
    t2.start();
 
    t1.join();
    t2.join();
 
    System.out.println(i);
  }
}
main 방법의 앞의 세 줄에 주의하여 키워드가 실례 방법에 작용하는 정확한 용법을 설명하십시오.
2.3 정적 방법에 직접 작용
synchronized 키워드를 static 방법에 사용하면 위의 예와 같이 두 라인이 같은 Runnable 방법을 가리키지 않아도 된다.방법 블록이 현재 클래스의 자물쇠를 요청해야 하기 때문에, 현재 실례가 아니라, 스레드 간에 정확하게 동기화할 수 있습니다.

public class AccountingSync implements Runnable {
  static int i = 0;
  public static synchronized void increase() {
    i++;
  }
 
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }
 
  public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new AccountingSync());
    Thread t2 = new Thread(new AccountingSync());
 
    t1.start();
    t2.start();
 
    t1.join();
    t2.join();
 
    System.out.println(i);
  }
}
3. 잘못된 자물쇠 추가
위의 예에서 우리는 만약에 우리가 계수기 응용을 필요로 한다면 데이터의 정확성을 확보하기 위해서 우리는 당연히 계수기에 자물쇠를 채워야 한다는 것을 안다. 따라서 우리는 아래의 코드를 쓸 수 있다.

public class BadLockOnInteger implements Runnable {
  static Integer i = 0;
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      synchronized (i) {
        i++;
      }
    }
  }
 
  public static void main(String[] args) throws InterruptedException {
    BadLockOnInteger badLockOnInteger = new BadLockOnInteger();
 
    Thread t1 = new Thread(badLockOnInteger);
    Thread t2 = new Thread(badLockOnInteger);
 
    t1.start();
    t2.start();
 
    t1.join();
    t2.join();
 
    System.out.println(i);
  }
}
우리가 위 코드를 실행할 때, 출력된 i가 매우 작다는 것을 발견할 수 있다.이것은 라인이 결코 안전하지 않다는 것을 설명한다.
이 문제를 설명하려면 Integer에서 말하자면 자바에서 Integer는 변하지 않는 대상에 속하고 String과 마찬가지로 대상이 일단 만들어지면 수정할 수 없다.만약 당신이 Integer=1을 가지고 있다면, 그것은 영원히 1.만약 당신이 이 상대를 = 2에게 양보하고 싶다면?Integer는 하나만 다시 생성할 수 있습니다.매번 i++ 다음에 Integer의 valueOf 방법을 호출한 것과 같습니다. Integer의 valueOf 방법의 원본을 보겠습니다.

public static Integer valueOf(int i) {
  if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
  return new Integer(i);
}
    Integer.valueOf () 는 사실상 공장 방법이다. 그는 새로운 Integer 대상을 되돌려 주고 값을 i에게 다시 복사하는 경향이 있다.
그래서 우리는 문제의 원인을 알 수 있다. 여러 개의 스레드 사이에서 i++ 이후 i는 모두 새로운 대상을 가리키기 때문에 스레드가 잠길 때마다 서로 다른 대상의 실례를 불러올 수 있다.해결 방법은 매우 간단합니다. 위의 3가지synchronize를 사용하는 방법 중 하나로 해결할 수 있습니다.

좋은 웹페이지 즐겨찾기