JAVA 공부일기1-14
Java의 정석 기초편
Chapter13 쓰레드
1. 프로세스와 쓰레드
프로세스: 실행 중인 프로그램. 실행에 필요한 메모리를 할당받아 프로세스가 됨. 프로세스는 프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원, 그리고 쓰레드로 구성.
쓰레드: 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것.
쓰레드는 사용자 쓰레드와 데몬 쓰레드가 있음. 데몬쓰레드는 다른 일반 쓰레드의 작업을 돕는 보조적인 역할 수행.
2. 멀티쓰레딩의 장단점.
장점: cpu 사용률 향상. 자원 효율적 사용. 사용자에 대한 응답성 향상. 작업이 분리되어 코드 간결.
단점: 동기화(synchronization), 교착상태(deadlock) 문제 고려 필요.
3. 쓰레드의 구현과 실행.
쓰레드 구현 방법: Thread클래스 상속 또는 Runnable인터페이스 구현. (Thread클래스를 상속받으면 다른 클래스를 상속받지 못해서 Runnable인터페이스를 구현하는 방법이 일반적.) 두 방법 모두 run()의 몸통을 채우는 것.
쓰레드 실행 방법: run()이 아닌 start()호출. start()를 호출하면 쓰레드가 실행대기 상태에 있다가 자신의 차례가 되면 실행됨. 한 번 실행이 종료된 쓰레드는 다시 실행할 수 없음.
main메서드에서 run()을 호출하는 것은 단순 메서드 호출. start()는 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택을 생성한 다음에 run()을 호출해서, 새로운 호출스택에 run()이 첫 번째로 올라감.
4. 싱글쓰레드와 멀티쓰레드
멀티쓰레드에서 쓰레드 간의 작업 전환(context switching)에 시간이 걸림.
싱글코어에서 단순 CPU만을 사용하는 계산 작업에서는 오히려 싱글쓰레드가 효율적이며, 두 쓰레드가 서로 다른 자원을 사용하는 작업은 멀티쓰레드 프로세스가 더 효율적. (ex. 사용자로부터 데이터를 입력받는 작업, 네트워크로 파일을 주고받는 작업, 외부기기와의 입출력이 필요한 작업...)
I/O 블락킹: 쓰레드가 입출력(I/O)처리를 위해 기다리는 것.
5. 쓰레드의 우선순위
쓰레드는 우선순위라는 속성을 가짐. 우선순위의 값에 따라 쓰레드가 얻는 실행시간이 달라짐. 작업의 중요도에 따라 쓰레드의 우선순위를 다르게 지정해 특정 쓰레드가 더 많은 작업시간을 갖도록 할 수 있음.
우선순위의 범위는 1~10이며, 숫자↑ 우선순위↑. main메서드 내의 쓰레드의 우선순위는 자동으로 5가 됨.
→ 멀티코어에서는 쓰레드의 우선순위에 따른 차이가 거의 없음. 멀티코어라 해도 OS마다 다른 방식으로 스케줄링하며, 자바는 쓰레드가 우선순위에 따라 어떻게 처리해야하는지 강제하지 않으므로 JVM마다 쓰레드의 우선순위와 관련된 구현이 다를 수 있음.
6. 데몬쓰레드
데몬 쓰레드는 다른 일반 쓰레드의 작업을 돕는 보조적인 역할 수행. 일반 쓰레드가 모두 종료되면 데몬 쓰레드는 강제적으로 자동 종료. (ex) 가비지 컬렉터, 워드프로세서의 자동 저장...)
일반 쓰레드의 작성,실행 방법과 같으며, 실행 전 setDaemon(true) 호출.
7. 쓰레드의 상태
상태 | 설명 |
NEW | 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태 |
RUNNABLE | 실행중 또는 실행 가능한 상태 |
BLOCKED | 동기화 블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태) |
WAITINIG, TIMED_WAITING | 쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은 일시정지 상태. TIMED_WAITING은 일시정지시간이 지정된 경우를 의미. |
TERMINATED | 쓰레드의 작업이 종료된 상태. |
8. 쓰레드의 실행제어
메서드 | 설명 |
static void sleep(long millis) static void sleep(long millis, int nanos) |
지정된 시간 동안 쓰레드 일시정지. 지정된 시간이 지나고 나면 자동으로 다시 실행대기상태가 됨. (현재 실행 중인 쓰레드에 작동.) |
void join() void join(long millis) void join(long millis, int nanos) |
지정된 시간동안 쓰레드 실행. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속함. (현재 쓰레드가 아닌 특정 쓰레드에 동작.) |
void interrupt() | sleep()이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만듦. 해당 쓰레드에서는 Interrupted Exception이 발생함으로써 일시정지상태를 벗어남. → stop() 대신 쓰레드를 종료시키는데 주로 사용. 다만 쓰레드를 강제로 종료시키지 못하고, 작업을 멈추라고 요청. |
void stop() | 쓰레드 즉시 종료. |
void suspend() | 쓰레드 일시정지. resume()을 호출하면 다시 실행대기상태가 됨. |
void resume() | suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기 상태로 만듦. |
static void yield() | 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보하고 자신은 실행대기상태가 됨. |
* resume(), stop(), suspend()는 쓰레드를 교착상태(dead-lock)로 만들기 쉽기 때문에 deprecated됨.
교착상태(DeadLock)
둘 이상의 쓰레드가 서로의 작업이 끝나기만을 기다리며 작업을 더이상 진행하지 못하는 상태.
교착상태 필요조건: 상호배제, 비선점, 점유와 대기, 원형 대기
9. 쓰레드의 동기화
멀티쓰레드 프로세스에서 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에 서로의 작업에 영향을 줌.
→ 공유 데이터를 사용하는 코드 영역을 임계영역으로 지정하고, 공유 데이터가 가지고 있는 lock을 획득한 하나의 쓰레드만 이 영역 내의 코드를 수행할 수 있게 함. 해당 쓰레드가 lock을 반납하면 다른 쓰레드가 반납된 lock을 획득해 임계영역의 코드 수행 가능. 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 막는 것이 쓰레드의 동기화.
synchronized를 이용한 동기화
1. 메서드 전체를 임계 영역으로 지정: 메서드 앞에 synchronized를 붙여 메서드 전체를 임계영역으로 설정.
2. 특정 영역을 임계 영역으로 지정: 메서드 내의 코드 일부를 블럭으로 감싸고 블럭 앞에 synchronized(참조변수)를 붙임.
wait()과 notify()
특정 쓰레드가 객체의 락을 가진 상태로 오랜 시간을 보내지 않도록 해야함.
동기화된 임계 영역의 코드를 수행하다가 작업을 더이상 진행할 상황이 아니면, wait()을 호출해 쓰레드가 락을 반납하고 기다리게 함. 나중에 진행할 수 있는 상황이 되면, notify()를 호출해서 작업을 중단했던 쓰레드가 다시 락을 얻어 작업을 진행할 수 있게 함. (notify()는 waiting pool에서 대기 중인 쓰레드 중에서 하나를 임의로 선택해서 통지함.)