9. 쓰레드(Thread)
1) 프로세스와 쓰레드
- 프로그램: 실행 가능한 파일(HDD)
- 프로세스: 실행 중인 프로그램(메모리) -> 자원과 쓰레드로 구성
- 쓰레드: 프로세스 내 실제 작업 수행, 모든 프로세스는 하나 이상의 쓰레드 보유
- 싱글 쓰레드 프로세스: 자원+쓰레드
- 멀티 쓰레드 프로세스: 자원+N개의 쓰레드
2) 멀티 쓰레드
- 멀티쓰레딩: 하나의 프로세스 내에 여러 개의 쓰레드를 사용하는 것
- 장점: 효율적인 자원 사용, 사용자에 대한 응답성 향상, 작업 분리로 인해 간결한 코드
- 주의사항: 동기화에 의한 문제 발생, 교착 상태(Dead-Lock), 기아 상태
*프로그래밍 고려사항이 많은 게 단점으로 작용할 수 있음
- 싱글코어: 순차실행, 병행(Concurrent) // 병행 -> 번갈아가며 작업
- 멀티코어: 병행(Concurrent), 병렬(Parallel) // 병렬 -> 동시에 작업
병행: 작업을 번갈아가며 수행
병렬: 한 작업을 나눠서 수행
3) 쓰레드의 구현과 실행
- Thread 클래스 상속: 쓰레드의 메소드 오버라이딩 필요
- Runnable 인터페이스 구현: Thread 클래스의 메소드를 가져와서 사용해야 함
- 클래스의 다른 상속을 위해 인터페이스를 구현하는 방향이 낫다.
- public void run() {/*작업내용*/} // 쓰레드 생성(별도의 스택 생성)
- start() // 쓰레드 생성 후 start()를 호출해서 실행
4) 쓰레드의 우선순위
- 작업의 중요도에 따라 우선순위 조정 가능(처리 시간 조정/ 순서는 스케쥴러가 조정)
- void setPriority(int newPriority) // 쓰레드의 우선순위를 지정값으로 변경(10높음~1낮음)
- int getPriority() // 쓰레드의 우선순위 반환
5) 쓰레드 그룹
- 관련 쓰레드를 묶어서 다루기 위한 것
- 모든 쓰레드는 반드시 그룹에 포함(미지정시 main쓰레드 그룹에 포함)
- 조상 쓰레드의 그룹과 우선순위 상속
6) 데몬 쓰레드(Daemon Thread)
- 일반 쓰레드의 작업을 돕는 보조 역할 수행
- 일반 쓰레드가 모두 종료되면 자동 종료
- 가비지 컬렉터, 자동저장, 화면 자동갱신 등에 사용
- 무한루프와 조건문 이용 실행 후 대기하다가 특정 조건 만족시 작업 수행 후 다시 대기
7) 쓰레드의 상태
- NEW : 쓰레드가 생성되고 아직 START()가 호출되지 않은 상태
- RUNNABLE : 실행 중 또는 실행 가능 상태
- BLOCKED : 동기화 블럭에 의해 일시정지 상태
- WAITING : 작업이 종료되지는 않았지만 실행 불가(unrunnalbe) 일시정지 상태
- TIMED_WAITING : 일시정지 시간이 지정된 경우
- TERMINATED : 쓰레드의 작업이 종료된 상태
8) 쓰레드 실행 제어
- static void sleep(long millis, int nanos) : 1/1000초 단위로 쓰레드 일시 정지
*InterruptedException이 발생하면 깨어나기 때문에 예외처리 필수
- void join(long millis, int nanos) : 지정시간 동안 쓰레드 실행
- void interrupt() : 일시정지 상태의 쓰레드를 실행대기 상태로 변경
- boolean isInterrupted() : 쓰레드의 interrupted상태를 반환
- static boolean interrupted() : 현재 쓰레드의 interrupted 상태 반환 후 false로 변경
- void stop() : 쓰레드 즉시 종료
- void suspend() : 쓰레드 일시 정지
- void resume() : 일시 정지 상태의 쓰레드를 실행 대기 상태로 변경
- static void yield() : 실행 중 주어진 실행시간을 다른 쓰레드에게 양보 후 실행대기
*deprecated Method: stop, suspend, resume // Dead-Lock(교착 상태) 방지 목적
9) 쓰레드의 동기화
- 객체에 Lock을 걸어서 한 번에 하나의 쓰레드만 객체에 접근할 수 있도록 제한
- 목적: 데이터의 일관성 유지
- 특정 객체 대상 양식: synchronized(객체의 참조변수) {}
- 메소드 대상 양식: public synchronized void 메소드명() {}
- 동기화블럭 : synchronized(this) {연산내용} // 메소드 내에서 동기화시킬 구간 설정
10) wait()와 notify()
- 동기화된 영역의 코드를 수행 중에 더 이상 진행할 상황이 아니면 잠시 Lock을 반납해서 다른 작업을 수행 할 수 있도록 한다.
- Object에 정의되어 있음
- 동기화 블록 내에서만 사용 가능
- 기아(Starvation)현상: wait 이후 notify를 받지 못한 채 오랫동안 기다리는 상태
- wait() : Lock을 반납하고 기다림
- notify() : 수행을 중단했던 쓰레드를 임의호출해서 재진입(Reentrance)하도록 한다.
- notifyAll() : 수행을 중단했던 모든 쓰레드를 호출해서 재진입하도록 한다.(기아 방지)
11) Lock과 Condition을 이용한 동기화
- java.util.concurrent.locks패키지에서 제공하는 Lock클래스들을 이용한 동기화(JDK1.5)
- wait()와 notify()로는 불가능한 선별적인 통지 가능(경쟁상태 방지)
*경쟁 상태(Race Condition): 여러 쓰레드가 lock을 얻기 위해 서로 경쟁하는 것
- ReentrantLock: 재진입이 가능한 lock, 가장 일반적인 배타적인 lock
=> 무조건 lock이 있어야만 임계영역의 코드 수행 가능
- ReentrantReadWriteLock: 읽기에는 공유적, 쓰기에는 배타적인 lock
=> 중복해서 읽기 가능(읽기 lock이 걸린 상태에서 쓰기 lock 걸기 불가)
- stampedLock: ReentrantReadWriteLock에 낙관적 읽기의 기능을 추가(JDK1.8)
=> lock을 걸거나 해지할 때 스탬프(long type)를 사용,
*낙관적 읽기(Optimistic Reading Lock): 무조건 읽기 Lock을 걸지 않고 쓰기와 읽기가 충돌할때만 사용(쓰기가 끝난 후 읽기 Lock을 걸어줌)
12) ReentrantLock 클래스 사용
- 생성자: ReentrantLock() or ReentrantLock(boolean fair): 매개변수로 true 입력시 가장 오래된 쓰레드가 lock을 획득 할 수 있게 공정한 처리 => 확인 과정에서 성능이 떨어지기 때문에 대부분의 경우 공정함보다 성능을 선택
- Sychronized 대신 lock()과 unlock()을 사용
void lock(): 잠금
void unlock() : 잠금해제
boolean isLocked() : 잠겼는지 확인
- tryLock(): Lock을 얻기 위해 기다리지 않음
tryLock(): Lock을 얻으면 true, 얻지 못하면 false 반환
tryLock(long timeout, timeUnit unit) throws InterruptedException: 정해진 시간만큼만 기다렸다가 Lock을 얻으면 true, 얻지 못하면 false 반환 (생성자: 시간, 시간 타입)
13) Condition 클래스 사용
- 클래스에서 쓰레드 각각의 객체를 얻어 쓰레드 구분 통지에 사용
- Condition은 이미 생성된 lock으로부터 newCondition()을 호출해서 생성
ex)
private ReentrantLock lock = new ReentrantLock(); // 락 생성
private Condition forCook = lock.newCondition(); // 요리사를 위한 객체
private Condition forCust = lock.newCondition(); // 손님을 위한 객체
14) volatile
- CPU는 성능 향상을 위해 변수값을 코어의 cache에 저장해놓고 작업 -> 각 코어의 캐시와 메모리의 값이 불일치하는 현상이 발생
- 변수 앞에 volatile를 붙이면 캐시가 아닌 메모리에서 값을 읽기 때문에 불일치 해소
- volatile대신 synchronized블럭을 사용해도 동기화로 인해 같은 효과를 얻을 수 있음
- long과 double 타입의 변수는 하나의 명령어로 값을 읽거나 쓸 수 없음
-> 변수값을 읽는 과정에서 다른 쓰레드가 개입하지 못하게 하기 위해 volatile 사용
15) fork & join 프레임워크
- 멀티 쓰레드 프로그래밍을 위한 프레임워크(JDK1.7)
- RecursiveAction 또는 RecursiveTask를 상속받아서 구현
- RecursiveAction : 반환값이 없는 작업을 구현할 때 사용
- RecursiveTask : 반환값이 있는 작업을 구현할 때 사용
- 이용 방법: 위 두 클래스의 compute()라는 추상메소드 구현 후 invoke()로 구동
- ForkJoinPool은 fork & join프레임워크에서 제공하는 쓰레드 풀(Thread Pool)
특징: 각 쓰레드가 작업큐를 제공받아 각자에게 담긴 작업을 순서대로 처리
장점: 쓰레드 반복생성 or 과다 생성 방지, 작업 큐가 비어있는 쓰레드는 다른 쓰레드의 작업 큐를 가져와서 수행
- fork(): 해당 작업을 쓰레드 풀의 작업 큐에 넣는다(비동기 메소드)
- join(): 해당 작업의 수행이 끝나면 결과를 반환(동기 메소드)
- 과정: compute() 작업 분리 -> fork() 작업큐에 제공 -> join() 작업 결과 반환
'프로그래밍 > Java' 카테고리의 다른 글
[Java 요약 정리] 11. 스트림(Stream) (0) | 2018.12.27 |
---|---|
[Java 요약 정리] 10. 람다식(Lambda Expression) (0) | 2018.12.27 |
[Java 요약 정리] 8. 애너테이션(Annotation) (0) | 2018.12.26 |
[Java 요약 정리] 7. 열거형(Enums) (0) | 2018.12.26 |
[Java 요약 정리] 6. 지네릭스(Generics) (0) | 2018.12.26 |