자바에서 뮤텍스 객체 사용

1. 개요

이 튜토리얼에서는 Java로 뮤텍스를 구현하는 다양한 방법을 살펴 봅니다 .

2. 뮤텍스

다중 스레드 응용 프로그램에서는 두 개 이상의 스레드가 동시에 공유 리소스에 액세스해야하므로 예기치 않은 동작이 발생할 수 있습니다. 이러한 공유 리소스의 예로는 데이터 구조, 입출력 장치, 파일 및 네트워크 연결이 있습니다.

이 시나리오를 경쟁 조건이라고 합니다. 그리고 공유 리소스에 액세스하는 프로그램의 일부를 중요 섹션이라고 합니다. 따라서 경합 상태를 방지하려면 중요 섹션에 대한 액세스를 동기화해야합니다.

뮤텍스 (또는 상호 배제)는 가장 단순한 타입 동기 - 그것은 오직 하나 개의 스레드가 한번에 컴퓨터 프로그램의 중요한 부분을 실행할 수있는 것을 보장한다 .

임계 섹션에 액세스하기 위해 스레드는 뮤텍스를 획득 한 다음 임계 섹션에 액세스하고 마지막으로 뮤텍스를 해제합니다. 그 동안 다른 모든 스레드는 뮤텍스가 해제 될 때까지 차단됩니다. 스레드가 임계 섹션을 종료하자마자 다른 스레드가 임계 섹션에 들어갈 수 있습니다.

3. 왜 뮤텍스인가?

먼저, 매번 currentValue 를 하나씩 증가시켜 다음 시퀀스를 생성 하는 SequenceGeneraror 클래스 의 예를 살펴 보겠습니다 .

public class SequenceGenerator { private int currentValue = 0; public int getNextSequence() { currentValue = currentValue + 1; return currentValue; } }

이제 여러 스레드가 동시에 액세스하려고 할 때이 메서드가 어떻게 작동하는지 확인하는 테스트 케이스를 만들어 보겠습니다.

@Test public void givenUnsafeSequenceGenerator_whenRaceCondition_thenUnexpectedBehavior() throws Exception { int count = 1000; Set uniqueSequences = getUniqueSequences(new SequenceGenerator(), count); Assert.assertEquals(count, uniqueSequences.size()); } private Set getUniqueSequences(SequenceGenerator generator, int count) throws Exception { ExecutorService executor = Executors.newFixedThreadPool(3); Set uniqueSequences = new LinkedHashSet(); List
    
      futures = new ArrayList(); for (int i = 0; i < count; i++) { futures.add(executor.submit(generator::getNextSequence)); } for (Future future : futures) { uniqueSequences.add(future.get()); } executor.awaitTermination(1, TimeUnit.SECONDS); executor.shutdown(); return uniqueSequences; }
    

이 테스트 케이스를 실행하면 다음과 유사한 이유 때문에 대부분 실패 함을 알 수 있습니다.

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

uniqueSequences은 우리가 실행 한 횟수와 동일한 크기로 가정된다 getNextSequence의 우리의 테스트 케이스의 방법을. 그러나 경쟁 조건 때문에 그렇지 않습니다. 분명히 우리는 이러한 행동을 원하지 않습니다.

따라서 이러한 경쟁 조건을 방지하려면 한 번에 하나의 스레드 만 getNextSequence 메서드를 실행할 수 있도록해야합니다 . 이러한 시나리오에서는 뮤텍스를 사용하여 스레드를 동기화 할 수 있습니다.

다양한 방법이 있는데, 우리는 자바로 뮤텍스를 구현할 수 있습니다. 다음으로, SequenceGenerator 클래스에 대한 뮤텍스를 구현하는 다양한 방법을 살펴 보겠습니다 .

4. 동기화 된 키워드 사용

먼저 Java에서 뮤텍스를 구현하는 가장 간단한 방법 인 동기화 된 키워드에 대해 설명합니다 .

Java의 모든 객체에는 연관된 고유 잠금이 있습니다. 동기화 방법 및 동기화 블록이 고유 잠금을 사용 한 번에 하나의 스레드 임계 영역의 액세스를 제한 할 수 있습니다.

따라서 스레드가 동기화 된 메서드를 호출 하거나 동기화 된 블록에 들어가면 자동으로 잠금을 획득합니다. 메서드 또는 블록이 완료되거나 예외가 throw되면 잠금이 해제됩니다.

동기화 된 키워드 를 추가하여 getNextSequence 를 뮤텍스를 갖도록 변경해 보겠습니다 .

public class SequenceGeneratorUsingSynchronizedMethod extends SequenceGenerator { @Override public synchronized int getNextSequence() { return super.getNextSequence(); } }

동기화 블록은 유사하다 동기화 더 중요 섹션 제어 우리는 로크에 사용할 수있는 개체, 방법.

이제 동기화 된 블록을 사용하여 사용자 지정 뮤텍스 개체에서 동기화 하는 방법을 살펴 보겠습니다 .

public class SequenceGeneratorUsingSynchronizedBlock extends SequenceGenerator { private Object mutex = new Object(); @Override public int getNextSequence() { synchronized (mutex) { return super.getNextSequence(); } } }

5. ReentrantLock 사용

The ReentrantLock class was introduced in Java 1.5. It provides more flexibility and control than the synchronized keyword approach.

Let's see how we can use the ReentrantLock to achieve mutual exclusion:

public class SequenceGeneratorUsingReentrantLock extends SequenceGenerator { private ReentrantLock mutex = new ReentrantLock(); @Override public int getNextSequence() { try { mutex.lock(); return super.getNextSequence(); } finally { mutex.unlock(); } } }

6. Using Semaphore

Like ReentrantLock, the Semaphore class was also introduced in Java 1.5.

While in case of a mutex only one thread can access a critical section, Semaphore allows a fixed number of threads to access a critical section. Therefore, we can also implement a mutex by setting the number of allowed threads in a Semaphore to one.

Let's now create another thread-safe version of SequenceGenerator using Semaphore:

public class SequenceGeneratorUsingSemaphore extends SequenceGenerator { private Semaphore mutex = new Semaphore(1); @Override public int getNextSequence() { try { mutex.acquire(); return super.getNextSequence(); } catch (InterruptedException e) { // exception handling code } finally { mutex.release(); } } }

7. Using Guava's Monitor Class

So far, we've seen the options to implement mutex using features provided by Java.

However, the Monitor class of Google's Guava library is a better alternative to the ReentrantLock class. As per its documentation, code using Monitor is more readable and less error-prone than the code using ReentrantLock.

First, we'll add the Maven dependency for Guava:

 com.google.guava guava 28.0-jre 

Now, we'll write another subclass of SequenceGenerator using the Monitor class:

public class SequenceGeneratorUsingMonitor extends SequenceGenerator { private Monitor mutex = new Monitor(); @Override public int getNextSequence() { mutex.enter(); try { return super.getNextSequence(); } finally { mutex.leave(); } } }

8. Conclusion

이 튜토리얼에서는 뮤텍스의 개념을 살펴 보았습니다. 또한 Java로 구현하는 다양한 방법을 보았습니다.

항상 그렇듯이이 자습서에서 사용 된 코드 예제의 전체 소스 코드는 GitHub에서 사용할 수 있습니다.