자바에서 동기화 된 키워드 가이드

1. 개요

이 빠른 기사는 Java 에서 동기화 된 블록 을 사용하는 방법을 소개합니다 .

간단히 말해서, 다중 스레드 환경에서 두 개 이상의 스레드가 변경 가능한 공유 데이터를 동시에 업데이트하려고하면 경쟁 조건이 발생합니다. Java는 공유 데이터에 대한 스레드 액세스를 동기화하여 경합 상태를 방지하는 메커니즘을 제공합니다.

동기화 된 것으로 표시된 논리 는 동기화 된 블록이 되어 한 번에 하나의 스레드 만 실행할 수 있습니다 .

2. 왜 동기화인가?

합계를 계산하고 여러 스레드가 calculate () 메서드를 실행하는 일반적인 경쟁 조건을 고려해 보겠습니다 .

public class BaeldungSynchronizedMethods { private int sum = 0; public void calculate() { setSum(getSum() + 1); } // standard setters and getters } 

그리고 간단한 테스트를 작성해 보겠습니다.

@Test public void givenMultiThread_whenNonSyncMethod() { ExecutorService service = Executors.newFixedThreadPool(3); BaeldungSynchronizedMethods summation = new BaeldungSynchronizedMethods(); IntStream.range(0, 1000) .forEach(count -> service.submit(summation::calculate)); service.awaitTermination(1000, TimeUnit.MILLISECONDS); assertEquals(1000, summation.getSum()); }

우리는 단순히 compute ()를 1000 번 실행하기 위해 3- 스레드 풀이 있는 ExecutorService 를 사용하고 있습니다 .

이것을 직렬로 실행한다면, 예상되는 출력은 1000이 될 것이지만, 우리의 멀티 스레드 실행은 거의 매번 불일치 한 실제 출력으로 실패합니다 .

java.lang.AssertionError: expected: but was: at org.junit.Assert.fail(Assert.java:88) at org.junit.Assert.failNotEquals(Assert.java:834) ...

물론이 결과는 예상치 못한 것이 아닙니다.

경쟁 조건을 피하는 간단한 방법은 동기화 된 키워드 를 사용하여 작업을 스레드로부터 안전하게 만드는 것 입니다.

3. 동기화 된 키워드

동기화 키워드는 다른 수준에서 사용할 수 있습니다 :

  • 인스턴스 방법
  • 정적 방법
  • 코드 블록

동기화 된 블록 을 사용할 때 내부적으로 Java는 동기화를 제공하기 위해 모니터 잠금 또는 고유 잠금이라고도하는 모니터를 사용합니다. 이러한 모니터는 개체에 바인딩되므로 동일한 개체의 모든 동기화 된 블록은 동시에 실행되는 스레드를 하나만 가질 수 있습니다.

3.1. 동기화 된 인스턴스 방법

메서드 선언에 동기화 된 키워드를 추가하기 만하면 메서드가 동기화됩니다.

public synchronized void synchronisedCalculate() { setSum(getSum() + 1); }

메서드를 동기화하면 실제 출력이 1000으로 테스트 케이스가 통과됩니다.

@Test public void givenMultiThread_whenMethodSync() { ExecutorService service = Executors.newFixedThreadPool(3); SynchronizedMethods method = new SynchronizedMethods(); IntStream.range(0, 1000) .forEach(count -> service.submit(method::synchronisedCalculate)); service.awaitTermination(1000, TimeUnit.MILLISECONDS); assertEquals(1000, method.getSum()); }

인스턴스 메서드는 메서드를 소유 한 클래스의 인스턴스를 통해 동기화 됩니다. 즉, 클래스의 인스턴스 당 하나의 스레드 만이 메서드를 실행할 수 있습니다.

3.2. 동기화 Stati C 방법

정적 메서드는 인스턴스 메서드와 마찬가지로 동기화됩니다 .

 public static synchronized void syncStaticCalculate() { staticSum = staticSum + 1; }

이러한 메소드는 클래스와 관련된 Class 객체 에서 동기화 되며, 클래스 당 JVM 당 하나의 Class 객체 만 존재 하므로 인스턴스 수에 관계없이 클래스 당 정적 동기화 된 메소드 내에서 하나의 스레드 만 실행할 수 있습니다 .

테스트 해 보겠습니다.

@Test public void givenMultiThread_whenStaticSyncMethod() { ExecutorService service = Executors.newCachedThreadPool(); IntStream.range(0, 1000) .forEach(count -> service.submit(BaeldungSynchronizedMethods::syncStaticCalculate)); service.awaitTermination(100, TimeUnit.MILLISECONDS); assertEquals(1000, BaeldungSynchronizedMethods.staticSum); }

3.3. 메서드 내에서 동기화 된 블록

때로는 전체 메서드를 동기화하지 않고 그 안에있는 일부 명령 만 동기화하려고합니다. 이는 동기화를 블록 에 적용하여 달성 할 수 있습니다 .

public void performSynchronisedTask() { synchronized (this) { setCount(getCount()+1); } }

변경 사항을 테스트 해 보겠습니다.

@Test public void givenMultiThread_whenBlockSync() { ExecutorService service = Executors.newFixedThreadPool(3); BaeldungSynchronizedBlocks synchronizedBlocks = new BaeldungSynchronizedBlocks(); IntStream.range(0, 1000) .forEach(count -> service.submit(synchronizedBlocks::performSynchronisedTask)); service.awaitTermination(100, TimeUnit.MILLISECONDS); assertEquals(1000, synchronizedBlocks.getCount()); }

매개 변수 를 동기화 된 블록에 전달했습니다 . 이것은 모니터 개체이며 블록 내부의 코드는 모니터 개체에서 동기화됩니다. 간단히 말해, 모니터 개체 당 하나의 스레드 만 해당 코드 블록 내에서 실행할 수 있습니다.

메서드가 static 인 경우 객체 참조 대신 클래스 이름을 전달합니다. 그리고 클래스는 블록 동기화를위한 모니터가 될 것입니다.

public static void performStaticSyncTask(){ synchronized (SynchronisedBlocks.class) { setStaticCount(getStaticCount() + 1); } }

정적 메서드 내부에서 블록을 테스트 해 보겠습니다 .

@Test public void givenMultiThread_whenStaticSyncBlock() { ExecutorService service = Executors.newCachedThreadPool(); IntStream.range(0, 1000) .forEach(count -> service.submit(BaeldungSynchronizedBlocks::performStaticSyncTask)); service.awaitTermination(100, TimeUnit.MILLISECONDS); assertEquals(1000, BaeldungSynchronizedBlocks.getStaticCount()); }

3.4. 재진입

동기화 된 메소드 및 블록 뒤에있는 잠금 은 재진입됩니다. 즉, 현재 스레드는 동일한 동기화 된 잠금을 유지하는 동안 계속해서 획득 할 수 있습니다 .

Object lock = new Object(); synchronized (lock) { System.out.println("First time acquiring it"); synchronized (lock) { System.out.println("Entering again"); synchronized (lock) { System.out.println("And again"); } } }

위와 같이 동기화 된 블록 에있는 동안 동일한 모니터 잠금을 반복적으로 획득 할 수 있습니다.

4. 결론

이 빠른 기사에서는 동기화 된 키워드를 사용하여 스레드 동기화를 달성 하는 다양한 방법을 살펴 보았습니다 .

또한 경합 상태가 애플리케이션에 미치는 영향과 동기화가이를 방지하는 데 어떻게 도움이되는지 살펴 보았습니다. Java에서 잠금을 사용하는 스레드 안전성에 대한 자세한 내용은 java.util.concurrent.Locks 문서를 참조하십시오.

이 자습서의 전체 코드는 GitHub에서 사용할 수 있습니다.