Search

Java 동시성 문제 해결 방법들

태그
Java
면접질문
작성 상태
작성 완료
작성일
2023/12/07
참고 링크
참고 링크 2

동시성 문제

여러 스레드가 동시에 실행될 때 발생하는 문제. 교착 상태, 데드락이 대표적이다.
교착 상태란 여러 스레드가 같은 공유 자원에 동시에 접근해 각 스레드가 공유 자원의 무결성이 깨지는 상황을 의미한다.
데드락이란, 여러 스레드가 서로를 블로킹해 스레드가 영원히 실행되지 않는 상황을 의미한다.
이런 문제를 해결하기 위해서는 공유 자원에 접근하는 코드(= 임계 영역)에 접근 가능한 스레드를 제한하는 방법을 사용한다.

synchronized

이 키워드가 붙은 메서드나 블록을 모니터를 사용해 동시에 하나의 스레드만 접근할 수 있도록 한다.
public static void main(String[] args) { Test test = new Test(); ExecutorService executorService = Executors.newFixedThreadPool(5); executorService.execute(() -> { test.increase(); log.info(Thread.currentThread().getName() + " " + test.number + " "); }); executorService.execute(() -> { test.increase(); log.info(Thread.currentThread().getName() + " " + test.number + " "); }); executorService.execute(() -> { test.increase(); log.info(Thread.currentThread().getName() + " " + test.number + " "); }); }
Java
복사
class Test { int number; public synchronized void increase() { log.info(Thread.currentThread().getName() + " lock 보유 " + number); number++; log.info(Thread.currentThread().getName() + " lock 반환 " + number); } }
Java
복사
위와 같이 메서드에 synchronized 키워드가 붙은 경우 이 메서드는 동시에 하나의 스레드에서만 실행될 수 있고, 다른 스레드는 그 동안 블로킹된다. 따라서 다음 결과 처럼 하나의 스레드에 대해 lock 보유와 반환이 번갈아가며 등장한다.
동시성 제어가 없는 경우

단점

특정 스레드 입장에서, 해당 스레드가 락을 보유하고있는지 아닌지 코드로 알 수 없다.
즉, 현재 해당 스레드가 공유 자원에 접근할 수 없는 상태인지 아닌지 알 수 없고, 공유 자원에 접근할 수 없으면 무조건 블록된다.

Lock

일반적인 Mutex 를 구현한 것으로, Lock 을 보유한 스레드만 해당 자원에 접근할 수 있다.

ReentrantLock

synchronized 와 똑같이, ReentrantLock.lock() 를 호출한 이후 코드는 lock 을 가진 스레드만 실행할 수 있다. 락을 해제하기 위해 ReentrantLock.unlock() 를 호출한다.
class Test { private final ReentrantLock lock = new ReentrantLock(); int number; public void increase() { lock.lock(); log.info(Thread.currentThread().getName() + " lock 보유 " + number); number++; log.info(Thread.currentThread().getName() + " lock 반환 " + number); lock.unlock(); } }
Java
복사
boolean trylock() 메서드를 이용하면 lock 보유 실패 시 스레드가 블로킹되지 않고 다른 작업을 수행하도록 할 수 있다.
class Test { private final ReentrantLock lock = new ReentrantLock(); int number; public void increase() { if (!lock.tryLock()) { log.info(Thread.currentThread().getName() + " lock 보유 실패 " + number); return; } log.info(Thread.currentThread().getName() + " lock 보유 " + number); number++; log.info(Thread.currentThread().getName() + " lock 반환 " + number); if (lock.isLocked()) { lock.unlock(); } } }
Java
복사

ReentrantReadWriteLock

읽기 작업과 쓰기 작업에 별개로 Lock을 걸어 동시에 여러 스레드에서 읽는 것은 허용하지만, 쓰는 것은 허용하지 않을 수 있다.

Semaphore

여러개의 스레드가 임계 영역에 접근하는 것을 허용할 수 있다.