Thread
프로세스와 쓰레드
- 프로세스 : 실행중인 프로그램 (자원 + 쓰레드)
- 쓰레드 : 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것.
- 멀티쓰레드 프로세스 : 둘 이상의 쓰레드를 가진 프로세스
- 쓰레드는 작업을 수행할 개별적인 메모리 공간을 필요로 하기 때문에 프로세스의 메모리 한계에 따라 생성할 수 있는 쓰레드의 수가 결정된다.
멀티태스킹과 멀티 쓰레딩
- 멀티쓰레딩 : 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것.
- CPU의 코어가 한번에 단 하나의 작업만 수행할 수 있기때문에 실제로 동시에 처리되는 작업의 개수는 코어의 개수와 일치한다.
- 처리해야 하는 쓰레드의 수는 언제나 코어의 개수보다 훨씬 많기 때문에 각 코어가 아주 짧은 시간동안 여러 작업을 번갈아 가며 수행한다.
- 프로세스의 성능은 단순히 쓰레드의 개수에 비례하는 것이 아니며, 하나의 쓰레드를 가진 프로세스보다 두개의 쓰레드를 가진 프로세스가 오히려 더 낮은 성능을 보일 수도 있다.
멀티쓰레딩의 장점
- CPU의 사용률을 향상시킨다.
- 자원을 보다 효율적으로 사용할 수 있다.
- 사용자에 대한 응답성이 향상된다.
- 작업이 분리되어 코드가 간결해진다.
멀티쓰레딩의 단점
- 동기화
- 교착상태
쓰레드의 구현
쓰레드의 구현 방법은 Thread클래스를 상속받거나 Runnable인터페이스를 구현하는 방법이 있다. 일반적으로 Thread 클래스를 상속받으면 다른 클래스를 상속받을 수 없기 때문에 Runnable 인터페이스를 구현하는 방법이 일반적이다.
- Thread 클래스를 상속
class myThread1 extends Thread {
public void run() {
System.out.println(getName()); // 조상인 Thread의 getName 호출
}
}
- Runnable 인터페이스를 구현
class myThread2 implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName());
//현재 실행중인 쓰레드의 getName호출
}
}
- main문
public static void main() {
myThread1 t1 = new myThread1(); // myThread의 자손클래스의 인스턴스를 생성.
Runnable r = new myThread2(); //Runnable을 구현한 클래스의 인스턴스를 생성.
Thread t2 = new Thread(r); // 생성자 Thread(Runnable target)
}
위와같이 Runnable 인터페이스를 구현한 클래스의 인스턴스를 생성한 다음 Thread 클래스의 생성자의 매개변수로 제공해야 한다.
start()와 run()
- 쓰레드는 start()를 호출해야지 실행된다.
- start()시 즉시 실행이 아닌 실행대기 상태로 들어간다.
- 한번 종료된 쓰레드는 실행할 수 없다
- == 하나의 쓰레드에 대해 start()를 한 번만 호출할 수 있다.
- start()시 새로운 쓰레드가 실행하는데 필요한 호출스택을 생성한 다음 run()을 올린다.
쓰레드의 상태
- NEW : 쓰레드 객체는 생성되었지만, 아직 시작되지 않은 상태
- RUNNABLE : 쓰레드가 실행중인 상태
- BLOCKED : 쓰레드가 실행 중지 상태이며, 모니터 락이 풀리기를 기다리는 상태
- WAITING : 쓰레드가 대기중인 상태
- TIMED_WAITING : 특정 시간만큼 쓰레드가 대기중인 상태
- TERMINATED : 쓰레드가 종료된 상태
쓰레드의 우선순위
- 쓰레드는 우선순위(priority)라는 속성을 가지고 있다.
- 우선순위의 값에 따라 쓰레드가 얻는 실행시간이 달라진다.
- 우선순위 값의 범위는 1 ~ 10 이며 숫자가 높을수록 우선순위가 높다.
- 쓰레드의 우선순위는 쓰레드를 생성한 쓰레드로부터 상속받는다.
Main 쓰레드
- main 작업을 수행하는 쓰레드.
- 프로그램 실행시 기본적으로 하나의 쓰레드를 생성하고, 그 쓰레드가 main 메서드를 호출하여서 작업이 수행된다.
- main쓰레드 작업이 끝나도 다른 쓰레드가 남아있으면 프로그램이 종료되지 않는다.
- == 실행 중인 사용자 쓰레드가 1개도 없을 때 프로그램이 종료된다.
데몬 쓰레드
- 다른 일반쓰레드의 작업을 돕는 보조적인 역할을 하는 쓰레드
- 일반 쓰레드가 모두 종료되면 데몬 쓰레드도 강제 종료.
- ex) GC, 워드 프로세스의 자동저장, 화면자동갱신
- 무한루프와 조건문을 이용해서 실행 후 대기하고 있다가 특정 조건 만족시 작업을 수행하고 다시 대기.
- 일반 쓰레드와 작성방법과 실행방법이 같다.
- 쓰레드를 생성한 다음 실행하기 전에 setDaemon(true)를 호출하여야 한다.
- 데몬쓰레드가 생성한 쓰레드는 데몬 쓰레드.
쓰레드의 실행제어
- 쓰레드를 실행하고 start()를 호출시 실행대기열에 저장되어 자신의 차례가 올때까지 기다린다.(NEW)
- 자신의 차례가 되면 실행상태(RUNNABLE)가 된다
- 주어진 실행시간이 다되거나 yield()를 만나면 다시 실행대기 상태가 되고 다음 차례의 쓰레드가 실행상태가 된다.
- 실행중에 suspend(), sleep(), wait(), join(), I/O Block에 의해 일시정지 상태가 될 수 있다.
- 지정된 일시정지 시간이 다되거나(time out), notify(), resume(), interrupt()가 호출되면 일시정지 상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 된다.
- 실행을 모두 마치거나 stop()이 호출되면 쓰레드는 소멸된다.
- 위의 번호의 순서대로 쓰레드가 실행되는것은 아니다.
쓰레드의 동기화
- 멀티 쓰레드의 경우 여러 쓰레드가 같은 프로세스내의 자원을 공유해서 작업하기 때문에 서로의 작업에 영향을 주게된다.
- 이러한 일이 발생하는 것을 방지하기 위해서 한 쓰레드가 특정 작업을 끝마치기 전까지 다른 쓰레드에 의해 방해받지 않도록하는 것이 필요하다.
- 공유 데이터를 사용한 코드 영역을 임계 영역으로 지정하고, 공유 데이터가 가지고 있는 lock을 획득한 단 하나의 쓰레드만 이 영역 내의 코드를 수행할 수 있게 한다. 그리고 해당 쓰레드가 임계 영역 내의 모든 코드를 수행하고 벗어나서 lock을 반납해야만 다른 쓰레드가 반납된 lock을 획득하여 임계 영역의 코드를 수행할 수 있다.
- 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 ‘쓰레드의 동기화’ 라고 한다.
synchronized를 이용한 동기화
1. 메서드 전체를 임계 영역으로 저장
public synchronized void calcSum() {
// ...
}
2. 특정 영역을 임계영역으로 지정
synchronized(객체의 참조변수) {
//..
}
임계 영역은 멀티 쓰레드 프로그램의 성능을 좌우하기 때문에 메서드 전체에 거는 것보다 synchronized 블럭으로 임계 영역을 최소화 해서 보다 효율적인 프로그램이 되도록 노력해야 한다.
wait()와 notify()
- 특정 쓰레드가 객체의 락을 가진 상태로 오랜 시간을 보내지 않도록 하는 것도 중요.
- 동기화된 임계 영역의 코드를 수행하다가 작업을 진행할 상황이 아니면 wait()를 호출하여 쓰레드가 락을 반납하고 기다리게 한다.
- 그리고 나중에 작업을 진행할 수 있는 상황이 되면 notify()를 호출해서 작업을 중단했던 쓰레드가 다시 락을 얻어 작업을 진행할 수 있게 한다.
Lock과 Condition을 이용한 동기화
- jdk 1.5에 와서부터 java.util.concurrent.locks 패키지가 제공하는 lock클래스로 동기화가 가능해졌다.
- 쓰레드를 구별해서 통지하는 것이 가능.
- 같은 메서드내에서만 lock을 걸수 있는 syncronized 제약이 불편할때 lock 클래스 사용.
- ReentrantLock : 재진입이 가능한 lock, 가장 일반적인 배타 lock
- ReentrantReadWriterLock : 읽기에는 공유적이고 쓰기에는 배타적인 lock
- StampedLock : ReentrantReadWriterLock에 낙관적인 lock의 기능을 추가.
데드락
- 두 개 이상의 작업이 서로 상대방의 작업이 끝나기를 기다리고 있어서 아무것도 완료하지 못하는 상태를 말한다.
- 데드락은 교착상태의 4가지 조건이 동시에 만족할때 나타난다.
교착상태의 조건
- 상호배제
: 자원은 한번에 한 프로세스만이 사용할 수 있다. - 점유대기
: 최소한 하나의 자원을 점유하고 있으면서 다른 프로세스에 할당되어 사용되고 있는 자원을 추가로 점유하기 위해 대기하는 프로세스가 있어야 한다. 즉, 이미 자원을 사용중인데, 다른 프로세스가 사용중인 자원을 사용하기 위해 대기하고 있는 상태의 프로세스가 존재해야 한다. - 비선점
: 다른 프로세스에 할당된 자원은 사용이 끝날 때까지 강제로 빼앗을 수 없어야 한다. - 순환 대기
: 프로세스의 집합에서 P0은 P1이 점유한 자원을 대기하고 P1은 P2가 점유한 자원을 대기하고, P2...Pn-1d은 Pn이 점유한 자원을 대기하며 Pn은 P0이 점유한 자원을 요구해야 한다.