1. 동기화 (Synchronization)
- 멀티 쓰레드 프로세스는 여러 쓰레드가 하나의 프로세스 내에서 자원을 공유하여 작업하기 때문에 서로의 작업에 영향을 끼치게 된다. 이로 인한 문제들을 방지하기 위해 서로 간섭하지 못하도록 임계 영역(Critical Section)을 만들어 주는것을 쓰레드의 동기화(Synchroniztion)이라 한다.
- 임계 영역은 락(Lock)을 얻은 단 하나의 쓰레드만 출입이 가능하다. (객체 1개 당 락은 단 한 개만 존재한다.)
- synchronized로 임계 영역을 설정하는 두 가지 방법
1. 메소드 전체를 임계 영역으로 지정 : public synchronized void method() { }
2. 특정한 영역을 임계 영역으로 지정 : synchronized(객체의 참조변수) { }
2. Wait() & Notify()
- 멀티 쓰레드를 이용하는 이유가 작업을 효율적으로 처리하기 위해서인데, 동기화를 하게 되면 일을 진행하지 않고 기다리는 쓰레드들이 생겨, 오히려 역효과를 낼 수 있다. 이를 해결하기 위해 wait()과 notify() 메소드들 통해 락을 적절히 조율해준다.
- wait(), notify()는 Object 클래스 내에 정의된 메소드이며, 동기화 블록 내에서만 사용이 가능하다.
- wait() : 객체의 Lock을 풀고 쓰레드를 해당 객체의 waiting pool에 넣어준다.
- notify() : waiting pool에서 대기중인 쓰레드 중 하나를 깨운다.
- notifyAll() : waiting pool에서 대기중인 모든 쓰레드를 깨운다. - wait()과 notify()를 통해 락을 적절히 해결할 수 있어 보이나, 위에 설명과 같이 notify()는 waiting pool에 있는 특정 쓰레드가 아닌 임의의 쓰레드를 깨운다. 따라서 운이 나쁘면 특정 쓰레드는 계속해서 락을 얻지 못하고 기다리게 되는데, 이러한 현상을 기아(Starvation) 현상이라고 한다
- 기아 현상을 막기 위해 notifyAll()을 호출할 수도 있는데, 이는 모든 쓰레드를 깨우기 때문에 락을 얻기 위해 필요 이상의 경쟁이 발생한다. 이렇게 여러 쓰레드가 락을 얻기 위해 경쟁하는 상태를 경쟁 상태(Race Condition)이라고 한다.
- 이러한 문제점을 해결하기 위해 사용해야 할 것이 Lock과 Condition이다.
3. Lock
- 쓰레드의 동기화 방법에는 synchronized 키워드 외에도 'java.util.concurrent.locks' 패키지의 Lock 클래스들이 있다.
- Lock 클래스의 종류는 세 가지가 있다.
- ReentrantLock : synchronized와 가장 유사한 Lock 클래스로, 특정 조건에서 Lock을 풀고 다시 Lock을 얻고 임계 영역으로 들어와서 이후의 작업을 실행할 수 있다. 하나의 쓰레드가 Lock을 얻었다면 다른 쓰레드는 임계 영역에 접근할 수 없다. 가장 일반적인 배타 Lock 클래스이다.
- ReentrantReadWriteLock : 이름에서 유추 가능하듯, 읽기와 쓰기에 대한 구분이 있는 Lock 클래스이다. 읽기는 내용을 변경하지 않으므로 Lock을 중복해서 걸어도 된다는 뜻을 가지고 있다. 하지만 읽기 도중 쓰기를 한다거나, 쓰기 도중 읽기를 하는 것은 간섭이 되므로, 이는 허용되지 않는다.
- StampedLock : Lock을 걸거나 해지할 때, '스탬프(long타입의 정수값)'를 사용하며, ReentrantReadWriteLock의 개념에 '낙관적 읽기 Lock' 개념이 추가된 것이다. ReentrantReadWriteLock의 경우 읽기 중 쓰기를 하려면 읽기 Lock이 끝날 때까지 대기해야 하지만, '낙관적 읽기 Lock'은 읽기 중 쓰기가 Lock을 얻으려하면 쓰기에게 우선권을 양보하고, 다시 순서를 기다린다.
4. Condition
- synchronized 키워드의 wait() & notify()의 경우, 임의의 쓰레드를 대상으로 하기 때문에 사용자가 원하는 쓰레드를 고르지 못하는 문제가 있었다. 이를 개선한 것이 Condition 인터페이스이다. Condition 인터페이스는 이미 생성된 Lock으로부터 newCondition()을 호출하여 생성하기 때문에, 사용자가 원하는 쓰레드의 상태를 설정할 수 있다.
- Condition 인터페이스로 쓰레드의 상태를 조절할 때에는, wait() & notify() 대신에 await() & signal()을 사용한다.
public class ConditionExam {
private ReentrantLock lock = new ReentrantLock(); // ReentrantLock 으로 Lock 생성
private Condition myCondition = lock.newCondition(); // myCondition 생성
private Condition yourCondition = lock.newCondition(); // yourCondition 생성
public void method() {
lock.lock(); // 임계영역 시작
try {
myCondition.await(); // myCondition에서 Lock 회수
} catch (InterruptedException e) {
yourCondition.signal(); // yourCondition에 Lock 주입
} finally {
lock.unlock(); // 임계영역 종료
} // try..finally end
} // method() end
}
반응형
'JAVA > LIBRARY' 카테고리의 다른 글
| [JAVA] JSP Scriptlet (0) | 2023.02.17 |
|---|---|
| [JAVA] Servlet & JSP (0) | 2023.02.16 |
| [JAVA] Socket (0) | 2023.01.26 |
| [JAVA] Thread (2) - State, Scheduling, Priority, Daemon (0) | 2023.01.11 |
| [JAVA] Thread(1) - 개념 (0) | 2023.01.10 |
댓글