개발저장소
[Java] 스레드 - 2 본문
1. 동기화(Synchronized)
동기화는 하나의 작업이 완전히 완료된 후 다른 작업을 수행하는 것을 말한다. 이와 반대로 비동기는 하나의 작업 명령 이후 완료 여부와 상관없이 바로 다른 작업 명령을 수행하는 것을 말한다.
동기화 방법은 메서드 동기화와 블록 동기화가 있다.
메서드 동기화는 2개의 스레드가 동시에 메서드를 실행할 수 없다는 것, 블록 동기화는 2개의 스레드가 동시에 해당 블록을 실행할 수 없다는 것을 의미한다.
하나의 스레드가 공유 객체를 사용할 때, 다른 스레드가 사용할 수 없도록 하는 것을 락(lock)이라고 표현한다.
※ 트랜잭션: 업무의 최소 단위
메서드 동기화
동기화하고자 하는 메서드의 리턴 타입 앞에 synchronized 키워드를 넣는다.
class MyData {
int data = 3;
public synchronized void plusData() {
// data 1 증가 연산
}
}
블록 동기화
동기화는 하나의 스레드만 사용하기 때문에 성능 면에서 손해를 보게 된다. 그렇기 때문에 동기화할 영역을 한정해서 적용한다면 굳이 메서드 전체를 동기화할 필요가 없고, 이를 블록 동기화라고 한다.
class MyData {
int data = 3;
public void plusData() {
synchronized (this) {
// data 1 증가 연산
}
}
}
동기화의 원리
모든 객체는 Key를 하나씩 가지고 있다. 동기화를 사용하면 처음 사용하는 스레드가 Key를 가지고, 다른 스레드는 Key를 가질 수 없어 작업을 실행할 수 없다.
동기화 메서드의 Key는 자기 자신을 포함하는 인스턴스나 클래스의 것이다. 블록 동기화의 Key는 synchronized의 매개변수로 들어간 객체 (Key 객체)이다.
같은 Key로 묶인 메서드나 블록은 동시에 실행할 수 없다. 스레드가 이미 하나의 Key로 다른 메서드나 블록을 실행시키는 중이기 때문이다.
이를 상호 배제(Mutual Exclusion) 또는 뮤텍스(Mutex)라고 한다.
vs 세마포어(Semaphore)
여러 스레드가 한 번에 자원을 사용할 수 있도록 허용하는 메커니즘이다. 임의의 개수의 스레드가 자원을 사용할 수 있도록 허용한다. 특정 숫자 값을 가지고 있으며, 이 값이 0이 되면 다른 스레드는 자원을 사용할 수 없고 기다려야 한다.
2. 스레드의 상태
스레드는 객체가 생성, 실행, 종료되기까지 다양한 상태를 가진다. Thread.State 타입으로 정의되어 있고, getState() 메서드로 가져올 수 있다.
Thread.State는 enum 타입이며, NEW, RUNNABLE, TERMINATED, TIMED_WAITING, BLOCKED, WAITING이 존재한다.
NEW, RUNNABLE, TERMINATED
처음 스레드가 생성되면 NEW 상태를 가지고, start() 메서드로 실행하면 RUNNABLE 상태가 된다. 이 상태에서 실행과 실행 대기를 반복하며 CPU를 다른 스레드와 나눠 사용한다. 이후 run() 메서드가 종료되면 TERMINATED 상태가 된다.
RUNNABLE 상태에서는 TIMED_WAITING, BLOCKED, WAITING이라는 일시정지 상태로 전환될 수 있다.
Thread의 정적 메서드 yield()는 다른 스레드에게 CPU 사용을 인위적으로 양보하고 자신은 실행 대기 상태로 전환한다.
스레드 객체는 한번 TERMINATED 상태가 되면 다시 실행할 수 없다.
TIMED_WAITING
Thread.sleep(long millis) 또는 join(long millis)가 호출되면 스레드는 TIMED_WAITING 상태가 된다. 일정 시간 동안 일시정지 상태가 되는 것이다. 시간이 모두 지나거나 interrupt() 메서드가 호출되면 다시 RUNNABLE 상태가 된다.
join()은 인스턴스 메서드로, 스레드 A 객체.join(1000)과 같이 호출되면 스레드 A에게 CPU를 할당하고 멈춘다. 만약 A가 1초 이내에 실행을 끝난다면 멈췄던 스레드는 다시 RUNNABLE 상태가 된다.
두 메서드 모두 필수적으로 InterruptedException을 처리해줘야 하는데, interrupt() 메서드가 호출되면 이 예외가 발생하여 일시정지가 종료된다.
BLOCKED
먼저 실행 중인 다른 스레드의 완료를 기다리는 상태이다. 실행 중인 스레드가 Key를 반납하면 BLOCKED 상태에서 RUNNABLE 상태가 되어 해당 동기화 영역을 실행하게 된다.
BLOCKED 상태로 대기중인 여러 스레드는 순서가 없다. 먼저 도착한 순서따위는 중요하지 않고, Key가 반납되면 다시 Key를 차지하기 위해 여러 스레드가 경쟁해야 한다.
데드락(Dead Lock)
교착 상태를 말한다. 두 개의 스레드가 자원을 하나씩 가지고 있으면서도 상대의 자원을 원하며 상대방의 작업이 끝나기를 바라는 무한정 대기 상태에 빠진 것을 말한다.
데드락이 발생하기 위해선 4가지 조건이 충족되어야 한다.
- 상호 배제(Mutual Exclusion): 자원은 한 번에 하나의 프로세스만 사용 가능
- 점유 대기(Hold and Wait): 최소 하나의 자원을 보유하면서 다른 자원을 기다리는 프로세스가 있다.
- 비선점(Non-preemption): 자원을 강제로 뺏을 수 없고 자발적으로 해제될 때까지 기다린다.
- 순환 대기(Circular Wait): 프로세스 사이 자원을 기다리는 순환 체인이 있다.
WAITING
시간 정보가 없는 join() 메서드가 호출되거나 wait() 메서드가 호출되면 일시정지 상태가 된다.
일시정지 시간이 정해진 바가 없으므로 계속 같은 상태에 빠져있게 되는데, 어떻게 WAITING 상태가 되었는가에 따라 RUNNABLE로 돌아가는 방법도 다르다.
join() 메서드가 호출되었다면, 대상 스레드가 종료되거나 interrupt() 메서드가 호출되었을 때 RUNNABLE 상태로 돌아간다.
wait() 메서드가 호출되었다면, Object 클래스의 notify()나 notifyAll() 메서드를 통해 RUNNABLE 상태로 돌아간다. notify()는 하나의 스레드를, notifyAll()은 모든 WAITING 상태의 스레드를 RUNNABLE 상태로 전환시킨다.
wait(), notify(), notifyAll()은 모두 동기화 블록 내에서만 사용할 수 있다.
'Programming Language > Java' 카테고리의 다른 글
[Java] 스레드 - 1 (0) | 2024.05.13 |
---|---|
[Java] 예외 처리 (0) | 2024.05.10 |
[Java] 이너 클래스와 이너 인터페이스 (0) | 2024.05.09 |
[Java] 추상 클래스와 인터페이스 (0) | 2024.05.08 |
[Java] 상속 (0) | 2024.05.03 |