프로세스와 쓰레드
: '실행 중인 프로그램'
프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)을 할당받아 프로세스가 된다.
프로세스 == 자원(데이터 + 메모리) + 쓰레드
프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이 바로 쓰레드이다.
멀티쓰레딩의 장단점
장점
- CPU의 사용률을 향상시킨다.
- 자원을 보다 효율적으로 사용할 수 있다.
- 사용자에 대한 응답성이 향상된다.
- 작업이 분리되어 코드가 간결해진다.
단점
- 여러 쓰레드가 같은 프로세스 내에서 자원을 공유하면서 작업을 하기 때문에 발생할 수 있는
동기화(synchronization), 교착상태(deadlock)와 같은 문제들이 생길 수 있다.
구현과 실행
: Thread클래스를 상속받는 방법과 Runnable인터페이스를 구현하는 방법
일반적으로 인터페이스를 구현하는 방법을 쓴다.
Runnable인터페이스
: 재사용성이 높고 코드의 일관성을 유지할 수 있기 때문에 보다 객체지향적인 방법이다.
start()와 run()
: main메서드에서 run을 호출하는 것은 단순히 클래스에 선언된 메서드를 호출하는 것일 뿐이다.
반면에 start는 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택(call stack)을 생성한 다음에
run을 호출해서, 생성된 호출스택에 run이 첫번째로 올라가게 한다.
입력작업(a)와 출력작업(b)를 하나의 쓰레드로 처리하면 입력이 끝날 때까지 출력 작업은 실행되지 않는다.
멀티 쓰레드를 이용하면 입력하는 동안 출력 작업이 진행된다.
그러므로 작업시간이 줄어든다. 효율적인 cpu사용.
쓰레드의 우선순위
void setPriority(int newPriority) 쓰레드의 우선순위를 지정한 값으로 변경한다.
int getPriority() 쓰레드의 우선순위를 반환한다.
public final static int MIN_PRIORITY = 1; //최소우선순위
public final static int NORM_PRIORITY = 5; //보통우선순위
public final static int MAX_PRIORITY = 10; //최대우선순위
쓰레드가 가질 수 있는 우선순위의 범위는 1~10이며 숫자가 높을수록 우선순위가 높다.
main메서드 내에서 생성하는 쓰레드의 우선순위는 자동적으로 5가 된다.
멀티코어에서는 쓰레드의 우선순위에 따른 차이가 거의 아니 전혀 없다. 결국 우선순위에 차등을 두어 쓰레드를 실행시키는 것이 별 효과가 없었다.
쓰레드 그룹
: 서로 관련된 쓰레드를 그룹으로 다루기 위한것.
쓰레드 그룹을 생성해서 쓰레드를 그룹으로 묶어서 관리할 수 있다.
그룹에 다른 쓰레드 그룹을 포함 시킬 수 있다.
보안성의 이유로 도입된 개념으로, 자신이 속한 그룹이나 하위 그룹은 변경할 수 있지만 다른 그룹의 쓰레드를
변경할 수는 없다.
쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 기본적으로 자신을 생성한 쓰레드와 같은 그룹에 속하게 된다.
우리가 생성하는 모든 쓰레드 그룹은 main쓰레드 그룹의 하위 쓰레드 그룹이 되며, 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 자동적으로 main쓰레드 그룹에 속하게 된다.
데몬 쓰레드
: 일반 쓰레드의 작업을 돕는 보조적인 역할을 수행하는 쓰레드.
일반 쓰레드가 종료되면 데몬쓰레드는 강제적으로 종료된다. 그 이유는 데몬쓰레드는 일반쓰레드의 보조역할을 하는데 일반쓰레드가 종료되고 나면 데몬쓰레드의 존재의 의미가 없기 때문이다.
데몬쓰레드의 예로는 가비지 컬렉터, 워드프로세서의 자동저장, 화면자동갱신 등이 있다.
쓰레드의 상태
: 쓰레드는 생성된 후부터 종료될 때까지 여러 상태를 가질 수 있다.
- 쓰레드 생성 start 호출 >> 바로실행 x 실행대기열에 저장되어 자신의 차례가 될 때까지 대기. 실행대기열은 큐와 같은 구조.
- 실행기에상태에 있다가 자신의 차례가 되면 실행상태가 된다.
- 주어신 실행시간이 다되거나 yieid를 만나면 다시 실행대기상태 >> 다시 자기 차례를 기다린다.
- 실행 중에 4번을 메서드를 만나면 일시정지 상태가 될 수 있다. 입출력을 예로 입력이 완료되면 다시 실행대기상태가 된다.
- 지정된 일시정지가 다되거나 5번 메서드가 호출되면 일시정지상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다린다.
- 실행을 모두 마치거나 stop이 호출되면 쓰레드는 소멸한다.
쓰레드의 실행제어
: 쓰레드 프로그래밍이 어려운 이유는 동기화와 스케줄링 때문이다.
쓰레드의 스케줄링을 잘하기 위해서는 쓰레드의 상태와 관련 메서드를 잘 알아야 한다.
sleep()
: 지정된 시간동안 쓰레드를 멈추게 한다.
sleep에 의해 일시정지 상태가 된 쓰레드는 지정된 시간이 다 되거나 interrupt()가 호출되면, InterruptedException이 발생되어 잠에서 깨어나 실행대기 상태가 된다.
그래서 sleep을 호출할 때는 항상 try-catch문으로 예외를 처리해줘야 한다.
interrupt()
: 쓰레드에게 작업을 멈추라고 요청한다. 단지 멈추라고 요청만 하는 것일 뿐 쓰레드를 강제 종료시키지는 못한다.
join()
: 쓰레드 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 할 때 사용한다.
시간을 지정하지 않으면, 해당 쓰레드가 작업을 모두 마칠 때까지 기다리게 된다.
try{
th1.join(); //현재 실행중인 쓰레드가 쓰레드 th1의 작업이 끝날때까지 기다린다.
} catch(InterruptedException e){}
join을 사용하지 않았으면 main쓰레드가 바로 종료되어 작업소요시간을 체크할 수 없지만 join을 사용하여 main쓰레드가 th1,th2쓰레드가 작업을 마칠 때 까지 main스레드를 기다리게 했기 때문에 작업시간을 체크할 수 있었다.
yield()
: 쓰레드 자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보한다.
동기화
: 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 '쓰레드의 동기화(synchronization)'
wait()
: 쓰레드의 락을 반납하고 대기.
notify()를 호출해서, 작업을 중단했던 쓰레드가 다시 락을 얻어 작업을 진행.
wait에 의해 락을 반납했다가, 다시 락을 얻어서 임계 영역에 들어오는 것을 재진입(reentrance)이라고 한다.
- Object에 정의되어있다.
- 동기화 블록(synchronized블록) 내에서만 사용할 수 있다.
- 보다 효율적인 동기화를 가능하게 한다.
wait과 notify는 빵집 앞에 빵을 사려고 대기하는 사람들로 설명할 수 있다.
자신의 원하는 빵은 5시, 3시에 나온다. 손님 3과 손님 5는 자신이 원하는 빵이 나올 때까지 양보하다가 자신이 원하는 빵이 나오면 통보를 받고 빵을 사 간다.
차이가 있다면, 오래 기다린 쓰레드가 락을 얻는다는 보장이 없다는 것이다.
'Languages > JAVA' 카테고리의 다른 글
입출력 I/O (0) | 2020.04.18 |
---|---|
람다와 스트림 (0) | 2020.04.13 |
열거형(enum), 어노테이션 (0) | 2020.04.08 |
Java 제네릭스(Generics) (0) | 2020.04.08 |
컬렉션 프레임웍 (0) | 2020.04.06 |