반응형
Java Thread
- 프로세스는 '실행중인 프로그램'
- 데이터와 메모리, 자원, 쓰레드로 구성
- 실제로 작업을 수행하는 것이 쓰레드
- OS는 멀티 태스킹(다중 작업)
- 하나의 프로세스는 멀티 쓰레드
멀티 쓰레드 장점
- CPU 사용률 향상
- 사용자 응답성 향상
- 작업리 분리되어 코드 간결
- 자원을 공유하므로 동기화, 교착상태와 같은 문제 발생할 수 있음.
쓰레드 구현 방법
- Thread 상속 받는 방법
- class MyThread extends Thread {
public void run() {
}
}
MyThread th1 = new MyThread();
th1.start(); - Runnable 인터페이스 구현하는 방법
- class MyThread Implements Runnable {
public void run() {
}
}
Runnable r = new MyThread();
Thread th1 = new Thread(r);
th1.start();
- Runnable 인터페이스 구현하는 방법이 일반적, 다른 클래스 상속 받을수 없기 때문에
쓰레드 이름 가져오기
- Thread 클래스를 상속 받으면 조상인 Thread 클래스의 메서드 직접 호출 → getName()
- Runnable 인터페이스로 구현하면 static 메서드인 currentThread() 호출 후 getName()
쓰레드 이름 지정
- 생성자나 메서드 통해 지정
- //생성자
Thread(Runnable r, String name)
Thread(String name)
//메서드
void setName(String name)
쓰레드 실행 → start();
- 한번 실행이 종료된 쓰레드는 다시 실행할 수 없다
- 2번이상 호출하면 IllegalThreadStateException 발생
START()와 RUN()
- main메서드에서 run()을 호출 하는 것은 쓰레드를 실행 시키는 것이 아니라 단순히 메서드 호출한 것
- start() 호출은 호출스택 생성 → run() 호출하여 호출 스택에 run()이 첫번째로 올라감
- main메서드의 작업도 하나의 쓰레드이다.
- 실행중인 쓰레드가 없을 경우 프로그램은 종료된다.
싱글쓰레드, 멀티쓰레드
- 프로세스 또는 쓰레드 간의 작업 전화을 컨텍스트 스위칭이라고 한다
- 싱글 코어에서 단순히 CPU만을 사용하는 계산 작업이면 오히려 멀티쓰레드보다 싱글쓰레드가 더 효율적
- 여러 쓰레드가 번갈아가면서 작업을 처리하면 컨텍스트 스위칭 시간이 소요되서 오히려 안좋을수 있음
- 자바가 OS 독립적이라고 하지만 OS 종속적인 부분이 몇가지 있는데 쓰레드도 그중 하나
쓰레드의 우선순위
- 우선순위 속성을 가지고 있음
- void setPriority(int priority) // 우선 순위 지정
int getPriority() // 우선 순위 반환
//최대 우선 순위 = 10
//최소 우선 순위 = 1
//기본 우선 순위 = 5
쓰레드 그룹
- 쓰레드 그룹을 생성하여 묶어서 관리할 수 있음.
- 그룹에 포함 시키는 방법은 생성자를 이용
- Thread(ThreadGroup group, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
- 모든 쓰레드는 반드시 그룹에 포함 되어야 함
- 기본적으로 자신을 생성한 쓰레드와 같은 그룹으로 지정
- 자바 어플리케이션이 실행되면, JVM은 main과 system 쓰레드 그룹을 생성
- 우리가 생성하는 모든 쓰레드 그룹은 main쓰레드 그룹의 하위 쓰레드 그룹이 됨
- 그룹을 지정하지 않으면 main쓰레드 그룹에 속함
ThreadGroup getThreadGroup(); //자신이 속한 쓰레드 그룹 반환
데몬 쓰레드
- 작업을 돋는 보조적인 역할을 수행하는 쓰레드
- 일반 쓰레드가 종료되면 데몬쓰레드는 강제적으로 자동 종료
- 데몬쓰레드 예로는 가비지 컬렉션, 워드프로세서로 치면 자동 저장, 화면 갱신 등...
- 쓰레드 생성 및 실행방법은 같으며 실행 전에 setDaemon(true) 호출 하면 됨.
- setDaemon 메서드는 반드시 start()를 호출하기 전에 실행 되어야 한다.
- 아닐 경우 IllegalThreadStateException 발생
- getAllStackTraces() 실행 중 또는 대기상태, 즉 작업 완료되지 않은 모든 쓰레드의 호출 스택을 출력 할 수 있다.
쓰레드 실행 제어
- 쓰레드 프로그래밍이 어려운 이유는 동기화와 스케쥴링 때문
- 스케쥴링 메서드
- //지정된 시간 일시정지
sleep(long mills)
sleep(long mills, int nanos)
//지정된 시간 동안 실행
join()
join(long mills)
join(long mills, int nanos)
//일시정지된 쓰레드 깨워서 실행 대기상태로 만듦
interrupt()
//즉시 종료
stop()
//일시 정지
suspend()
//일시 정지 -> 실행 대기 상태
resume()
//다른 쓰레드에게 양보 -> 자신은 실행 대기 상태
yield()
- 쓰레드 상태
- NEW : 아직 start() 호출되지 않은 상태
- RUNNABLE : 실행 중 또는 실행 가능 상태
- BLOCKED : 동기화 블럭에 의해 일시 정지 상태
- WAITING, TIMED_WAITING : 종료되진 않았지만 실행 가능하지 않은 상태, 일시 정지 상태
- TERMINATED : 작업이 종료된 상태
Sleep()
- 참조변수를 이용해서 호출해도 현재 실행 중인 쓰레드가 영향을 받음.
- Thread.sleep 으로 호출 하는게 좋음
interrupt(), interrupted()
- interrupt() : 쓰레드에게 작업을 멈추라고 요청, 종료시키지는 못함
- 쓰레드의 interrupted 상태(인스턴스 변수)를 바꾸는 역할
- interrupted() : interrupt() 호출 여부 반환 후 false로 초기화
- 호출되었으면 true
- 호출되지 않았으면 false
- 쓰레드 클래스 안에서 확인해보면 isInterrupted가 true 일 때, interrupted() 호출하면 true를 반환한다. 다시한번 isInterrupted를 호출하면 그때는 false를 반환한다. 초기화 시켜서
- isInterrupted() : interrupt() 호출 여부 반환
suspend(), resume(), stop()
- suspend()와 stop() 교착상태를 일이크기 쉽게 작성되어 사용 권장되지 않음
yield()
- 다음 차례 쓰레드에게 양보
- yield(), interrupt() 적절히 사용하면 응답성을 높일 수 있음.
JOIN()
- 자신 쓰레드를 잠시 멈추고 다른 쓰레드가 작업을 수행하도록 할 때 JOIN 사용
- 시간을 지정하지 않으면 해당 쓰레드가 모두 마칠 때까지 기다림
쓰레드 동기화
- 멀티 쓰레드 프로세서의 경우 같은 프로세스 내의 자원을 공유해서 서로 작업에 영향을 주게 됨
- 한 쓰레드가 특정 작업을 마치기 전까지 방해 받지 않도록 하는것이 필요하여 도입된 개념이 임계 영역(ciritical section), 잠금(lock)
- java에서는 synchronized 블럭을 이용하는 방법으로 지원했었지만 JDK 1.5부터는 locks, atomic 패키지를 지원
- synchronized 이용
- //메서드 전체를 임계영역으로 지정
public synchronized void mathod() {
}
//특정 영역 지정
synchronized(객체의 참조 변수) {
}
- 모든 객체는 lock을 하나씩 가지고 있음.
- critical section은 프로그램의 성능을 좌우하기 때문에 최소화하는 것이 중요
wait(), notify()
- 특정 쓰레드가 객체의 락을 가진 상태로 오랜 시간을 보내지 않도록 하는 것이 중요
- 작업을 더 이상 진행할 상황이 아니면 일단 wait() 호출
- 매개변수가 있을 경우 지정된 시간 동안만 대기
- 다시 진행할 상황이 되면 notify()를 호출
- void wait()
void wait(long timeout)
void wait(long timeout, int nanos)
void notify()
void notifyAll()
- wating pool은 객체마다 존재
- notifyAll() 호출 된다고 모든 객체의 waiting pool에 있는 쓰레드가 깨워지는건 아님
- 호출된 객체의 waiting pool에 있는 쓰레드만 해당
기아 현상, 경쟁 상태
- 쓰레드 통지를 계속 받지 못하는 상황을 기아(starvation) 현상이라 함
- 이 현상을 막으려면 notify() 대신 notifyAll()을 사용
- 여러 쓰레드가 lock을 엳기 위해 서로 경쟁하는 것을 경쟁 상태(race condition)
- 쓰레드를 구별해서 통지하는 것이 필요
lock과 condition
- synchronized을 이용한 동기화 방법은 같은 메서드 내에서만 lock을 걸 수 있는 제약이 불편
- lock클래스를 사용하면 됨
- ReentrantLock → 재진입이 가능한 lock, 일반적인 배타적인 lock
- ReentrantReadWriteLock → 읽기에는 공유적, 쓰기에는 배타적
- 다른 쓰레드가 읽기 lock을 중복해서 걸고 읽기를 수행할 수 있다.
- 대신 읽기 lock은 중복으로 허용되지 않는다.
- 내용은 변경되지 않으므로 문제 되지 않는다
- StampedLock → ReentrantReadWriteLock에 낙관적인 lock의 기능 추가
- 읽기 lock이 걸려 있으면 쓰기 lock을 얻기 위해 읽기 lock이 풀릴 때까지 대기
- 읽기 lock은 쓰기 lock에 의해 바로 풀림.
- 쓰기가 끝난 후에 읽기 lock을 거는 것
ReentrantLock
ReentrantLock()
ReentrantLock(boolean fair) //가장 오래 기다린 쓰레드가 lock 획득
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
//임계 영역
} finally {
lock.unlock();
}
- tryLock() → 다른 쓰레드에 의해 lock이 걸려 있으면 얻으려고 기다리지 않음
- 지정된 시간만 기다림.
- 얻지 못하면 false 반환
condition
- await() → wait()라 생각
- signal() → notify()
- signalAll() → notifyAll()
ReentrantLock lock = new ReentrantLock();
private Condition forlock1 = lock.newCondition();
private Condition forlock2 = lock.newCondition();
volatile
- 멀티 코어 프로세서는 코어마다 별도의 캐시를 가지고 있다.
- 메모리에서 읽어온 값을 캐시에 저장하고 캐시에서 값을 읽어서 작업한다
- 메모리에 저장된 변수의 값이 변경되었는데도 캐시에 저장된 값이 갱신 되지 않아서 문제가 발생
- 이럴 때 이렇게
- boolean stopped = false; -> volatile boolean stopped = false;
- synchronized 블럭 사용해도 같은 효과
- 쓰레드가 synchronized블럭으로 들어갈때와 나올때 캐시와 메모리간 동기화가 이루어지기 때문
- volatile long sharedVal; // long 타입의 변수(8byte)를 원자화
fork, join 프레임웍
- JDK 1.7부터 추가
- 하나의 작업을 작은 단위로 나눠서 여러 쓰레드가 동시에 처리하는 것을 쉽게 만들어줌
- RecursiveAction, RecursiveTask 두 클래스 중에서 하나 상속 받아 구현
- RecursiveAction → 반환값이 없는 작업을 구현할 때 사용
- RecursiveTask → 반환값이 있는 작업을 구현할 때 사용
- 두 클래스는 cimpute()라는 추상 메서드가 있고 추상 메서드를 구현하기만 하면됨
class testTask extends RecursiveTask<Long> {
public Long compute() {
// 로직 구현
}
}
참고
- Java의 정석 3rd Edition
반응형
'Java' 카테고리의 다른 글
[Java] #Formating, 포맷 (0) | 2018.04.16 |
---|---|
[Java] #Time, Date, 시간, 날짜 (2) | 2018.04.16 |