목표
자바의 멀티쓰레드 프로그래밍에 대해 학습하세요.
학습할 것 (필수)
- Thread 클래스와 Runnable 인터페이스
- 쓰레드의 상태
- 쓰레드의 우선순위
- Main 쓰레드
- 동기화
- 데드락
Thread 클래스와 Runnable 인터페이스
쓰레드를 구현하는 방법은 Thread클래스를 상속받는 방법과 Runnable 인터페이스를 구현하는 방법, 두 가지가 있다.
상속을 받으면 다른 클래스를 상속받을 수 없기 때문에, Runnable 인터페이스를 구현하는 방법이 일반적이다.
1. Thread 클래스 상속
class MyThread extends Thread {
public void run() {} // Thread클래스의 run()을 오버라이딩
}
2. Runnable 인터페이스 구현
class MyThread implements Runnable{
public void run() {} // Runnable 인터페이스의 run()을 구현
}
인스턴스 생성 방법
// Thread클래스를 상속받은 경우
ThreadExtends t1 = new ThreadExtend(); // Thread의 자손 클래스의 인스턴스 생성
// Runnable인터페이스를 구현한 경우
Runnable r = new ThreadInterface(); // Runnable을 구현한 클래스의 인스턴스를 생성
Thread t2 = new Thread(r); // 생성자 Thread(Runnable target)
쓰레드의 상태
상태 | 상수 | 설명 |
객체 생성 | NEW | 객체 생성. 아직 start() 메소드가 호출되지 않은 상태 |
실행 대기 | RUNNABLE | 실행 상태로 언제든지 갈 수 있는 상태 |
일시 정지 | WAITING | 다른 쓰레드가 동지할 때까지 기다리는 상태 |
TIMED_WAITING | 주어진 시간동안 기다리는 상태 | |
BLOCKED | 사용하고자 하는 객체의 락이 풀릴 떄까지 기다리는 상태 | |
종료 | TERMINATED | 실행을 마친 상태 |
- start()를 호출하면 쓰레드가 실행될 수 있는 상태가 된다.
- 호출 시 실행 대기 중인 쓰데르가 하나도 없으면 바로 실행상태가 되며, 실행대기 중인 쓰레드가 있다면 자신의 차례가 되어야 실행된다.
- 이때 쓰레드의 실행 순서는 OS의 스케쥴러가 작성한 스케쥴에 의해 결정된다.
- 한번 실행이 종료된 쓰레드는 다시 실행할 수 없다.
- 실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.
start()와 run()
run()
쓰레드를 실행시키는 것이 아니라 단순히 클래스에 선언된 메서드를 호출하는 것이다.
strat()
새로운 쓰레드가 작업을 실행하는데 필요한 호출 스택을 생성한 다음 run()을 호출해서, 생성된 호출 스택에 run()이 첫 번째로 올라가게 한다.
쓰레드의 우선순위
쓰레드는 우선순위(priority)라는 속성을 가지고 있다. 이 값에 따라 쓰레드가 얻는 실행시간이 달라진다.
쓰레드가 수행하는 작업 중요도에따라 각 쓰레드의 우선순위를 다르게 지정할 수 있다.
void setPriority(int newPriority) // 쓰레드의 우선순위를 지정한 값으로 변경한다.
int getPriority() // 쓰레드의 우선순위를 반환한다.
public static final int MAX_PRIORITY = 10 //최대우선순위
public static final int MIN_PRIORITY = 1 //최소우선순위
public static final int NORM_PRIORITY = 5 //보통우선순위
- 쓰레드가 가질 수 있는 우선순위의 범위는 1~10이다.
- 숫자가 높을수록 우선순위가 높다.
- 쓰레드를 생성한 쓰레드로부터 우선순위를 상속받는다.
- main메서드를 수행하는 쓰레드는 우선순위가 5이므로, main메서드 내에서 생성하는 쓰레드의 우선순위는 자동적으로 5가 된다.
Main 쓰레드
프로그램을 실행하면 기본적으로 하나의 쓰레드가 생성된다.
그 쓰레드가 main메서드를 호출해서 작업이 수행되도록 하고, 이를 Main 쓰레드라고 부른다.
동기화 (Synchronization)
한 쓰레드가 진행 중인 작업을 다른 스레드가 간섭하지 못하도록 막는 것.
- 공유 데이터를 사용하는 코드 영역을 임계 영역으로 지정해 놓는다.
- 공유 데이터(객체)가 가지고 있는 lock을 획득한 단 하나의 쓰레드만 이 영역 내의 코드를 수행할 수 있게 한다.
- 해당 쓰레드가 임계 영역 내의 모든 코드를 수행하고 벗어나서 lock을 반납한다.
- 반납된 lock을 다른 쓰레드가 획득하여 임계 영역의 코드를 수행한다.
synchronized를 이용한 동기화
1. 메서드 전체를 임계 영역으로 지정
public synchronized void calcSum(){
// 임계 영역(critial section)
}
ex)
public synchronized void withdraw(int money){
if(balance >= money){
//
}
}
2. 특정한 영역을 임계 영역으로 지정
synchronized(객체의 참조변수){
// 임계 영역(critial section)
}
ex)
public void withdraw(int money){
synchronized(this){
if(balance >= money){
//
}
}
}
임계 영역은 멀티쓰레드 프로그램의 성능을 좌우하기 때문에 메서드 전체에 락을 거는 것보다 synchronized블럭으로 임계 영역을 최소화하는 것이 좋다.
wait()와 notify()
특정 쓰레드가 객체의 락을 가진 상태로 오랜 시간을 보내는 상황을 개선하기 위해 고안된 것.
동기화된 임계 영역의 코드를 수행하다 작업을 더 이상 진행할 상황이 아닐 때 wait()을 호출하여 쓰레드가 lock을 반납하고 기다리게 한다.
나중에 작업을 진행할 수 있는 상황이 되면 notify()를 호출하여 다시 lock을 얻어 작업을 진행한다.
Lock과 Condition을 이용한 동기화
Lock
- java.util.concurrent.locks 패키지에 존재
- JDK1.5부터 추가
- synchronized는 같은 메서드 내에서만 lock을 걸 수 있다는 제약 사항을 해결하기 위해 사용.
ReentrantLock | 재진입이 가능한 lock. 가장 일반적인 배타 lock | 무조건 lock이 있어야만 임계 영역 수행이 가능하다. | ||
ReentrantReadWriteLock | 읽기에는 공유적이고, 쓰기에는 배타적인 lock | lock이 걸려있을때 다른 쓰레드가 읽기 lock을 중복으로 걸 수 있다. 쓰기는 중복으로 걸 수 없다. |
||
StampedLock | ReentrantReadWriteLock에 낙관적인 lock의 기능을 추가 | ReentrantReadWriteLock + 쓰기와 읽기가 충돌할 때만 쓰기가 끝난 후에 읽기 lock을 건다. |
Condition
각 쓰레드를 위한 Condition을 만들어 각각의 waiting pool에서 따로 기다리게 한다.
wait()¬ify()가 쓰레드를 구분하여 통지하지 못한다는 단점을 보완한 것이다.
Object | Condition |
void wait() | void await() void awaitUninterruptibly() |
void wait(long timeout) | boolean await(long time, TimeUnit unit) long awaitNanos(long nanosTimeout) boolean awaitUntil(Date deadline) |
void notify() | void signal() |
void notifyAll() | void signalAll() |
▲ wait()&nofity()와 await()&signel() 비교
데드락
두 개 이상의 작업이 서로 상대의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태
교착 상태의 예방
- 상호 배제 조건의 제거
교착 상태는 두 개 이상의 프로세스가 공유 가능한 자원을 사용할 때 발생하는 것이므로 공유 불가능한, 즉 상호 배제 조건을 제거하면 교착 상태를 해결할 수 있다.
- 점유와 대기 조건의 제거
한 프로세스에 수행되기 전에 모든 자원을 할당시키고 나서 점유하지 않을 때에는 다른 프로세스가 자원을 요구하도록 하는 방법이다. 자원 과다 사용으로 인한 효율성, 프로세스가 요구하는 자원을 파악하는 데에 대한 비용, 자원에 대한 내용을 저장 및 복원하기 위한 비용, 기아 상태, 무한 대기 등의 문제점이 있다.
- 비선점 조건의 제거
비선점 프로세스에 대해 선점 가능한 프로토콜을 만들어 준다.
- 환형 대기 조건의 제거
자원 유형에 따라 순서를 매긴다.
이 교착 상태의 해결 방법들은 자원 사용의 효율성이 떨어지고 비용이 많이 드는 문제점이 있다.
교착 상태의 회피
자원이 어떻게 요청될지에 대한 추가 정보를 제공하도록 요구하는 것으로 시스템에 circular wait가 발생하지 않도록 자원 할당 상태를 검사한다.
교착 상태 회피하기 위한 알고리즘으로 크게 두 가지가 있다.
- 자원 할당 그래프 알고리즘 (Resource Allocation Graph Algorithm)
- 은행원 알고리즘 (Banker's algorithm)
교착 상태의 무시
예방 혹은 회피 기법을 프로그래밍해서 넣으면 성능에 큰 영향을 미칠 수 있게 된다. 그렇기 때문에 데드락의 발생 확률이 비교적 낮은 경우 별다른 조치를 취하지 않는다.
교착 상태의 발견
감시/발견을 하는 detection 알고리즘으로 Deadlock 발생을 체크하는 방식. 이 역시 성능에 큰 영향을 미칠 수 있다.
Referrence)
자바의 정석 - 남궁 성
bit.ly/3w7yPrl (위키백과-교착상태)
'IT > JAVA' 카테고리의 다른 글
자바 애노테이션 - 백기선님 스터디 12주차 과제 (0) | 2021.04.24 |
---|---|
자바 Enum - 백기선님 스터디 11주차 과제 (0) | 2021.04.02 |
자바 예외처리 - 백기선님 스터디 9주차 과제 (0) | 2021.03.16 |
자바 인터페이스 - 백기선님 스터디 8주차 과제 (0) | 2021.03.01 |
자바 패키지 - 백기선님 스터디 7주차 과제 (0) | 2021.02.26 |