티스토리 뷰

Thread Safe 개념

Thread Safe란 멀티 스레드 프로그래밍에서
함수, 변수, 객체 등이 여러 스레드로부터 동시에 접근이 이루어져도
프로그램의 실행에 문제가 없다는 것을 뜻합니다.
하나의 함수가 한 Thread로부터 호출되어 실행 중일 때
다른 Thread가 그 함수를 호출하여 동시에 함께 실행되더라도
각 스레드에서의 함수의 수행 결과가 의도한대로 나와야합니다.
제일 좋은 방법은 자원을 공유하지 않는 코드를 작성하는 것이 좋습니다.

 

Thread Safe하지 않은 코드 작성하기

 

수열을 생성하는 SequenceGenerator를 구현합니다.

이 코드는 currentValue를 Update하는데, Thread가 동시에 접근할 경우, 의도하지 않은 결과가 나올 수 있습니다.

예를 들어, currentValue가 29일 때, 동시에 Thread가 Update하는 함수에 접근한다면 의도한 결과는 31이지만,

Thread가 동시에 접근했기 때문에 Thread 모두가 29를 30으로 Update합니다.

이를 동시에 접근했을 때 의도하지 않은 결과가 나오므로 Thread Safe하지 않은 코드라고 말할 수 있습니다.

public class SequenceGenerator {

    private int currentValue = 0;
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public int getNextSequence() {
    	// 이부분은 2개의 연산으로 이루어집니다.
        // currentValue + 1 연산
        // currentValue + 1 결과를 대입하는 연산
        currentValue = currentValue + 1;

        return currentValue;
    }
}

 

여러 Thread가 동시에 접근할 경우를 재현하기 위해서 getUniqueSequences()를 만듭니다.

private Set<Integer> getUniqueSequences(SequenceGenerator generator, int count) throws Exception {
  ExecutorService executor = Executors.newFixedThreadPool(3);
  // Set을 활용하여 중복된 수열을 제거
  Set<Integer> uniqueSequences = new LinkedHashSet<>();
  List<Future<Integer>> futures = new ArrayList<>();

  for (int i = 0; i < count; i++) {
  	futures.add(executor.submit(generator::getNextSequence));
  }

  for (Future<Integer> future : futures) {
  	uniqueSequences.add(future.get());
  }

  executor.awaitTermination(1, TimeUnit.SECONDS);
  executor.shutdown();

  return uniqueSequences;
}

 

SequenceGenerator가 Thread Safe 하지 않다는 테스트 코드를 작성합니다.

Thread Safe하지 않은 코드이기 때문에 의도한 결과가 일정하게 나오지 않습니다.

간혹 의도한 결과가 나와서 테스트가 통과하지 않는 경우도 발생합니다.

@Test
public void givenUnsafeSequenceGenerator_whenRaceCondition_thenUnexpectedBehavior() throws Exception {
  Set<Integer> uniqueSequences = getUniqueSequences(new SequenceGenerator(), COUNT);
  assertNotEquals(COUNT, uniqueSequences.size());
}

 

Thread Safe한 코드 작성하기

Synchronized 함수 사용

Synchronized 함수를 사용하면 클래스로 만들어진 하나의 인스턴스를 기준으로 동기화가 이루어진다.

public class SequenceGeneratorUsingSynchronizedMethod extends SequenceGenerator {

    @Override
    public synchronized int getNextSequence() {
        return super.getNextSequence();
    }
}

Synchronized 블럭 사용

Synchronized 블럭을 사용하면 전달받은 객체를 기준으로 동기화가 이루어진다.

여기서는 mutex라는 Object를 기준으로 블럭 안의 코드를 한 쓰레드만이 실행할 수 있다.

public class SequenceGeneratorUsingSynchronizedBlock extends SequenceGenerator {

    private Object mutex = new Object();

    @Override
    public int getNextSequence() {
        synchronized (mutex) {
            return super.getNextSequence();
        }
    }
}

 

ReentrantLock 사용

ReentrantLock을 사용하면 시작점과 끝점을 명백히 명시할 수 있습니다.

Synchronized는 암묵적이고 ReentrantLock는 명시적이라는 차이가 있습니다.

public class SequenceGeneratorUsingReentrantLock extends SequenceGenerator {

    private ReentrantLock mutex = new ReentrantLock();

    @Override
    public int getNextSequence() {
        try {
            mutex.lock(); // 동기화 시작
            return super.getNextSequence(); // 동기화 대상
        } finally {
            mutex.unlock(); // 동기화 끝
        }
    }
}

 

세마포어 사용

하나의 스레드만 임계구역에 들어가면 성능 이슈가 발생하는데, 세마포어는 임계구역에 여러 스레드가 들어갈 수 있는 장점이 있습니다.

공유자원이 2개 이상일 때 잘못 사용하면 서로 자원을 점유하기 위해서 대기상태에 빠지므로 DeadLock이 발생할 수 있습니다.

public class SequenceGeneratorUsingSemaphore extends SequenceGenerator {

    private Semaphore mutex = new Semaphore(1);

    @Override
    public int getNextSequence() {
        try {
            mutex.acquire();
            return super.getNextSequence();
        } catch (InterruptedException e) {
            throw new RuntimeException("Exception in critical section.", e);
        } finally {
            mutex.release();
        }
    }
}

 

모니터 사용

모니터는 2개의 Queue가 존재한다.

하나의 Queue는 하나의 Thread만 공유자원에 접근할 수 있게하는 역할을 한다.(상호배타)

다른 Queue는 임계구역에 진입한 wait()을 통해 Thread가 블락되면 새로운 Thread가 진입할 수 있도록 알려주는 역할을 한다.(조건동기)

그리고 새로 진입한 Thread가 notify()를 통해 블락된 Thread를 재진입할 수 있도록 하는 역할도 한다.(조건동기)

public class SequenceGeneratorUsingMonitor extends SequenceGenerator {

    private Monitor mutex = new Monitor();

    @Override
    public int getNextSequence() {
        mutex.enter();
        try {
            return super.getNextSequence();
        } finally {
            mutex.leave();
        }
    }
}

 

https://www.baeldung.com/java-mutex

 

Using a Mutex Object in Java | Baeldung

Explore the concept of a mutex object in Java.

www.baeldung.com

https://sycho-lego.tistory.com/11

 

세마포어(Semaphore)와 뮤텍스(Mutex)

여러 쓰레드들은 자원을 공유하고, 프로세스간 메시지를 전송하면서 간혹 문제가 발생할 수 있습니다. 즉, 공유된 자원에 여러 프로세스 , 쓰레드가 동시에 접근하면서 문제가 발생합니다. 공유된 자원 속 하나의..

sycho-lego.tistory.com

https://copycode.tistory.com/83

 

운영체제 13장 - 프로세스 관리(10) : 모니터 -

운영체제 13장 - 프로세스 관리(10) : 모니터 - 동기화 문제를 해결하기 위해서 우리는 세마포라는 도구를 사용하였다. 하지만 동기화 문제를 해결하는데 세마포만이 사용되지는 않는다. 사실 세마포의 경우 오래..

copycode.tistory.com

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함