Java 다중 루틴 프로그램에서synchronized 수식 방법의 사용 실례

15518 단어 Javasynchronized
자바 5 이전에는 synchronized 키워드로 자물쇠를 잠그는 기능을 사용했다.
synchronized 키워드는 방법의 수식자(동기화 방법)로 사용할 수도 있고 함수 내의 문장(동기화 코드 블록)에 작용할 수도 있다.
synchronized를 파악하는 데 관건은 그 물건을 자물쇠로 하는 것을 파악하는 것이다.클래스의 비정적 방법(구성원 방법)에 대해 말하자면 대상의 실례를 얻는 자물쇠를 의미한다.클래스의 정적 방법(클래스 방법)에 대해 말하자면 클래스의 클래스 대상의 자물쇠를 얻어야 한다.동기화 코드 블록에 대해 취득한 객체의 잠금을 지정합니다.동기화 비정적 방법은 전체 방법을 포함하는synchronized (this) {...} 코드 블록으로 볼 수 있습니다.  
동기화 코드 블록이든 동기화 방법이든 매번 하나의 라인만 들어갈 수 있다. (같은 시간에 최대 하나의 라인만 이 단락의 코드를 실행한다.)만약 다른 라인이 (같은 동기화 블록이든 다른 동기화 블록이든) 들어가려고 한다면, jvm는 그것들을 걸어 놓을 것이다.이런 구조는 병발 이론에서 임계구(critical section)라고 부른다.
jvm 내부에서 효율을 높이기 위해 동시에 실행되는 모든 스레드에는 처리하고 있는 데이터의 캐시 복사본이 있습니다. 우리가synchronzied를 사용하여 동기화할 때 진정으로 동기화되는 것은 서로 다른 스레드에서 잠긴 대상을 표시하는 메모리 블록입니다. (복사 데이터는 메인 메모리와 동기화를 유지하는데 왜 동기화라는 단어를 사용해야 하는지 알겠죠.) 간단하게 말하면 동기화 블록이나 동기화 방법이 실행된 후에잠긴 대상에 대한 모든 수정은 자물쇠를 풀기 전에 메인 메모리로 써야 합니다.동기화 블록에 들어가 자물쇠를 얻은 후에 잠긴 대상의 데이터는 메인 메모리에서 읽히고 자물쇠를 가진 라인의 데이터 사본은 반드시 메인 메모리의 데이터 보기와 동기화된다.
다음은 구체적인 예를 들어synchronized의 각종 상황을 설명한다.
동기화 방법
먼저 동기화 방법의 예를 살펴보겠습니다.

public class SynchronizedTest1 extends Thread 
{ 
  private synchronized void testSynchronizedMethod() 
  { 
    for (int i = 0; i < 10; i++) 
    { 
      System.out.println(Thread.currentThread().getName() 
          + " testSynchronizedMethod:" + i); 
 
      try 
      { 
        Thread.sleep(100); 
      } 
      catch (InterruptedException e) 
      { 
        e.printStackTrace(); 
      } 
    } 
  } 
 
  @Override 
  public void run() 
  { 
    testSynchronizedMethod(); 
  } 
 
  public static void main(String[] args) 
  { 
 
        SynchronizedTest1 t = new SynchronizedTest1(); 
    t.start(); 
    t.testSynchronizedMethod(); 
  } 
} 
이 프로그램을 실행하면 다음과 같은 결과가 나옵니다.

main testSynchronizedMethod:0 
main testSynchronizedMethod:1 
main testSynchronizedMethod:2 
main testSynchronizedMethod:3 
main testSynchronizedMethod:4 
main testSynchronizedMethod:5 
main testSynchronizedMethod:6 
main testSynchronizedMethod:7 
main testSynchronizedMethod:8 
main testSynchronizedMethod:9 
Thread-0 testSynchronizedMethod:0 
Thread-0 testSynchronizedMethod:1 
Thread-0 testSynchronizedMethod:2 
Thread-0 testSynchronizedMethod:3 
Thread-0 testSynchronizedMethod:4 
Thread-0 testSynchronizedMethod:5 
Thread-0 testSynchronizedMethod:6 
Thread-0 testSynchronizedMethod:7 
Thread-0 testSynchronizedMethod:8 
Thread-0 testSynchronizedMethod:9 
testSynchronizedMethod 방법이 두 라인 사이에서 동기화되는 것을 볼 수 있습니다.
만약main 방법을 다음과 같이 수정한다면 두 라인은 동기화할 수 없습니다. 왜냐하면 두 라인의 동기화 모니터는 같은 대상이 아니기 때문에 동기화 역할을 할 수 없습니다.

public static void main(String[] args) 
  { 
    Thread t = new SynchronizedTest1(); 
    t.start(); 
     
    Thread t1 = new SynchronizedTest1(); 
    t1.start(); 
  } 

출력 결과는 다음과 같습니다.

Thread-0 testSynchronizedMethod:0 
Thread-1 testSynchronizedMethod:0 
Thread-0 testSynchronizedMethod:1 
Thread-1 testSynchronizedMethod:1 
Thread-0 testSynchronizedMethod:2 
Thread-1 testSynchronizedMethod:2 
Thread-0 testSynchronizedMethod:3 
Thread-1 testSynchronizedMethod:3 
Thread-0 testSynchronizedMethod:4 
Thread-1 testSynchronizedMethod:4 
Thread-0 testSynchronizedMethod:5 
Thread-1 testSynchronizedMethod:5 
Thread-0 testSynchronizedMethod:6 
Thread-1 testSynchronizedMethod:6 
Thread-0 testSynchronizedMethod:7 
Thread-1 testSynchronizedMethod:7 
Thread-0 testSynchronizedMethod:8 
Thread-1 testSynchronizedMethod:8 
Thread-0 testSynchronizedMethod:9 
Thread-1 testSynchronizedMethod:9 
수정된main 방법이 두 라인 사이에서 동기화되려면 testSynchronizedMethod 방법을 정적 방법으로 성명해야 합니다. 이렇게 하면 두 라인의 모니터가 같은 대상(클래스 대상)이고 동기화되어 실행될 수 있습니다.수정된 코드는 다음과 같습니다.

public class SynchronizedTest1 extends Thread 
{ 
  private static synchronized void testSynchronizedMethod() 
  { 
    for (int i = 0; i < 10; i++) 
    { 
      System.out.println(Thread.currentThread().getName() 
          + " testSynchronizedMethod:" + i); 
 
      try 
      { 
        Thread.sleep(100); 
      } 
      catch (InterruptedException e) 
      { 
        e.printStackTrace(); 
      } 
    } 
  } 
 
  @Override 
  public void run() 
  { 
    testSynchronizedMethod(); 
  } 
 
  public static void main(String[] args) 
  { 
    Thread t = new SynchronizedTest1(); 
    t.start(); 
     
    Thread t1 = new SynchronizedTest1(); 
    t1.start(); 
  } 
} 
출력 결과는 다음과 같습니다.

Thread-0 testSynchronizedMethod:0 
Thread-0 testSynchronizedMethod:1 
Thread-0 testSynchronizedMethod:2 
Thread-0 testSynchronizedMethod:3 
Thread-0 testSynchronizedMethod:4 
Thread-0 testSynchronizedMethod:5 
Thread-0 testSynchronizedMethod:6 
Thread-0 testSynchronizedMethod:7 
Thread-0 testSynchronizedMethod:8 
Thread-0 testSynchronizedMethod:9 
Thread-1 testSynchronizedMethod:0 
Thread-1 testSynchronizedMethod:1 
Thread-1 testSynchronizedMethod:2 
Thread-1 testSynchronizedMethod:3 
Thread-1 testSynchronizedMethod:4 
Thread-1 testSynchronizedMethod:5 
Thread-1 testSynchronizedMethod:6 
Thread-1 testSynchronizedMethod:7 
Thread-1 testSynchronizedMethod:8 
Thread-1 testSynchronizedMethod:9 
동기화 블록의 상황은 동기화 방법과 유사하지만 동기화 블록은 동기화 제어의 입도를 축소시켜 다선정 병행 실행의 효율을 더욱 잘 발휘할 수 있다.
this 객체를 사용하여 동일한 객체 인스턴스 간의 동기화를 제어합니다.

public class SynchronizedTest2 extends Thread 
{ 
  private void testSynchronizedBlock() 
  { 
    synchronized (this) 
    { 
      for (int i = 0; i < 10; i++) 
      { 
        System.out.println(Thread.currentThread().getName() 
            + " testSynchronizedBlock:" + i); 
 
        try 
        { 
          Thread.sleep(100); 
        } 
        catch (InterruptedException e) 
        { 
          e.printStackTrace(); 
        } 
      } 
    } 
  } 
 
  @Override 
  public void run() 
  { 
    testSynchronizedBlock(); 
  } 
 
  public static void main(String[] args) 
  { 
    SynchronizedTest2 t = new SynchronizedTest2(); 
    t.start(); 
 
    t.testSynchronizedBlock(); 
  } 
} 
출력 결과:

main testSynchronizedBlock:0 
main testSynchronizedBlock:1 
main testSynchronizedBlock:2 
main testSynchronizedBlock:3 
main testSynchronizedBlock:4 
main testSynchronizedBlock:5 
main testSynchronizedBlock:6 
main testSynchronizedBlock:7 
main testSynchronizedBlock:8 
main testSynchronizedBlock:9 
Thread-0 testSynchronizedBlock:0 
Thread-0 testSynchronizedBlock:1 
Thread-0 testSynchronizedBlock:2 
Thread-0 testSynchronizedBlock:3 
Thread-0 testSynchronizedBlock:4 
Thread-0 testSynchronizedBlock:5 
Thread-0 testSynchronizedBlock:6 
Thread-0 testSynchronizedBlock:7 
Thread-0 testSynchronizedBlock:8 
Thread-0 testSynchronizedBlock:9 
class 객체를 사용하여 서로 다른 인스턴스 간의 동기화를 제어합니다.

public class SynchronizedTest2 extends Thread 
{ 
  private void testSynchronizedBlock() 
  { 
    synchronized (SynchronizedTest2.class) 
    { 
      for (int i = 0; i < 10; i++) 
      { 
        System.out.println(Thread.currentThread().getName() 
            + " testSynchronizedBlock:" + i); 
 
        try 
        { 
          Thread.sleep(100); 
        } 
        catch (InterruptedException e) 
        { 
          e.printStackTrace(); 
        } 
      } 
    } 
  } 
 
  @Override 
  public void run() 
  { 
    testSynchronizedBlock(); 
  } 
 
  public static void main(String[] args) 
  { 
    Thread t = new SynchronizedTest2(); 
    t.start(); 
 
    Thread t2 = new SynchronizedTest2(); 
    t2.start(); 
  } 
} 

출력 결과:

Thread-0 testSynchronizedBlock:0 
Thread-0 testSynchronizedBlock:1 
Thread-0 testSynchronizedBlock:2 
Thread-0 testSynchronizedBlock:3 
Thread-0 testSynchronizedBlock:4 
Thread-0 testSynchronizedBlock:5 
Thread-0 testSynchronizedBlock:6 
Thread-0 testSynchronizedBlock:7 
Thread-0 testSynchronizedBlock:8 
Thread-0 testSynchronizedBlock:9 
Thread-1 testSynchronizedBlock:0 
Thread-1 testSynchronizedBlock:1 
Thread-1 testSynchronizedBlock:2 
Thread-1 testSynchronizedBlock:3 
Thread-1 testSynchronizedBlock:4 
Thread-1 testSynchronizedBlock:5 
Thread-1 testSynchronizedBlock:6 
Thread-1 testSynchronizedBlock:7 
Thread-1 testSynchronizedBlock:8 
Thread-1 testSynchronizedBlock:9 
 
synchronized 키워드를 사용하여 동기화 제어를 할 때 대상 모니터를 잘 파악해야 합니다. 모니터를 얻는 프로세스만 실행할 수 있고 다른 것은 모니터를 가져오기를 기다려야 합니다.모든 비null의 대상은 대상 모니터로 사용할 수 있으며,synchronized가 방법에 작용할 때 잠긴 것은 대상 실례(this)이다.정적 방법에 작용할 때 잠긴 것이 바로 대상에 대응하는 Class 실례이다
두 라인이 한 대상에 동시에 접근하는 동기화 방법
두 개의 병렬 스레드가 같은 대상의 동기화 방법에 접근할 때, 한 개의 스레드만 실행될 수 있다.다른 스레드는 현재 스레드가 실행되기를 기다려야 실행할 수 있습니다.

public class TwoThread {
  public static void main(String[] args) {
    final TwoThread twoThread = new TwoThread();

    Thread t1 = new Thread(new Runnable() {
      public void run() {
        twoThread.syncMethod();
      }
    }, "A");
    Thread t2 = new Thread(new Runnable() {
      public void run() {
        twoThread.syncMethod();
      }
    }, "B");

    t1.start();
    t2.start();
  }

  public synchronized void syncMethod() {
    for (int i = 0; i < 5; i++) {
      System.out.println(Thread.currentThread().getName() + " : " + i);
      try {
        Thread.sleep(500);
      } catch (InterruptedException ie) {
      }
    }
  }

}

출력 결과:

A : 0
A : 1
A : 2
A : 3
A : 4
B : 0
B : 1
B : 2
B : 3
B : 4
두 라인이 접근하는 것은 두 대상의 동기화 방법이다
이런 상황에서synchronized는 작용하지 않고 일반적인 방법과 같다.대응하는 자물쇠는 각자의 대상이기 때문이다.

public class TwoObject {
  public static void main(String[] args) {
    final TwoObject object1 = new TwoObject();
    Thread t1 = new Thread(new Runnable() {
      public void run() {
        object1.syncMethod();
      }
    }, "Object1");
    t1.start();

    final TwoObject object2 = new TwoObject();
    Thread t2 = new Thread(new Runnable() {
      public void run() {
        object2.syncMethod();
      }
    }, "Object2");
    t2.start();
  }

  public synchronized void syncMethod() {
    for (int i = 0; i < 5; i++) {
      System.out.println(Thread.currentThread().getName() + " : " + i);
      try {
        Thread.sleep(500);
      } catch (InterruptedException ie) {
      }
    }
  }

}

출력 결과 중 하나:

Object2 : 0
Object1 : 0
Object1 : 1
Object2 : 1
Object2 : 2
Object1 : 2
Object2 : 3
Object1 : 3
Object1 : 4
Object2 : 4
두 라인이 접근하는 것은synchronized의 정적 방법이다
이런 상황은 클래스가 잠겨 있기 때문에 언제든지 이 정적 방법은 하나의 라인만 실행할 수 있다.
동시 접근 동기화 방법과 비동기화 방법
한 라인이 대상의 동기화 방법에 접근할 때, 다른 라인은 여전히 이 대상의 비동기화 방법에 접근할 수 있다.

public class SyncAndNoSync {
  public static void main(String[] args) {
    final SyncAndNoSync syncAndNoSync = new SyncAndNoSync();

    Thread t1 = new Thread(new Runnable() {
      public void run() {
        syncAndNoSync.syncMethod();
      }
    }, "A");
    t1.start();

    Thread t2 = new Thread(new Runnable() {
      public void run() {
        syncAndNoSync.noSyncMethod();
      }
    }, "B");
    t2.start();
  }

  public synchronized void syncMethod() {
    for (int i = 0; i < 5; i++) {
      System.out.println(Thread.currentThread().getName() + " at syncMethod(): " + i);
      try {
        Thread.sleep(500);
      } catch (InterruptedException ie) {
      }
    }
  }

  public void noSyncMethod() {
    for (int i = 0; i < 5; i++) {
      System.out.println(Thread.currentThread().getName() + " at noSyncMethod(): " + i);
      try {
        Thread.sleep(500);
      } catch (InterruptedException ie) {
      }
    }
  }

}

가능한 출력 결과:

B at noSyncMethod(): 0
A at syncMethod(): 0
B at noSyncMethod(): 1
A at syncMethod(): 1
B at noSyncMethod(): 2
A at syncMethod(): 2
B at noSyncMethod(): 3
A at syncMethod(): 3
A at syncMethod(): 4
B at noSyncMethod(): 4
같은 대상에 접근하는 다른 동기화 방법
하나의 라인이 하나의 대상의 동기화 방법 A에 접근할 때, 다른 라인은 이 대상의 모든 다른 동기화 방법에 대한 접근이 막힐 것이다.첫 번째 라인은 이미 대상 자물쇠를 얻었기 때문에 다른 라인은 자물쇠를 얻지 못하면 서로 다른 방법에 접근하지만 자물쇠를 얻지 못하고 접근할 수 없다.

public class TwoSyncMethod {
  public static void main(String[] args) {
    final TwoSyncMethod twoSyncMethod = new TwoSyncMethod();

    Thread t1 = new Thread(new Runnable() {
      public void run() {
        twoSyncMethod.syncMethod1();
      }
    }, "A");
    t1.start();

    Thread t2 = new Thread(new Runnable() {
      public void run() {
        twoSyncMethod.syncMethod2();
      }
    }, "B");
    t2.start();
  }

  public synchronized void syncMethod1() {
    for (int i = 0; i < 5; i++) {
      System.out.println(Thread.currentThread().getName() + " at syncMethod1(): " + i);
      try {
        Thread.sleep(500);
      } catch (InterruptedException ie) {
      }
    }
  }

  public synchronized void syncMethod2() {
    for (int i = 0; i < 5; i++) {
      System.out.println(Thread.currentThread().getName() + " at syncMethod2(): " + i);
      try {
        Thread.sleep(500);
      } catch (InterruptedException ie) {
      }
    }
  }

}

출력 결과:

A at syncMethod1(): 0
A at syncMethod1(): 1
A at syncMethod1(): 2
A at syncMethod1(): 3
A at syncMethod1(): 4
B at syncMethod2(): 0
B at syncMethod2(): 1
B at syncMethod2(): 2
B at syncMethod2(): 3
B at syncMethod2(): 4

좋은 웹페이지 즐겨찾기