1. 쓰레드


프로세스와 쓰레드

  • 모든 프로세스는 최소한 하나의 쓰레드를 갖고 있음
    • 프로세스 : 실행 중인 프로그램, 자원과 쓰레드로 구성
    • 쓰레드 : 프로세스 내에서 실제 작업을 수행
  • 프로세스 : 쓰레드 = 공장 : 일꾼
    • 싱글 쓰레드 프로세스 = 자원 + 쓰레드
    • 멀티 쓰레드 프로세스 = 자원 + 쓰레드 + 쓰레드 + … + 쓰레드
  • “하나의 새로운 프로세스를 생성하는 것 보다 하나의 새로운 쓰레드를 생성하는 것이 더 적은 비용이 든다.”

멀티쓰레드의 장단점

  • 대부분의 프로그램이 멀티쓰레드로 작성되어 있다. 그러나, 멀티쓰레드 프로그래밍이 장점만 있는 것은 아니다.

장점

  • 시스템 자원을 보다 효율적으로 사용할 수 있음
  • 사용자에 대한 응답성이 향상됨
  • 작업이 분리되어 코드가 간결해짐

단점

  • 동기화에 주의해야 함
  • 교착상태(dead-lock)가 발생하지 않도록 주의해야 함
  • 각 쓰레드가 효율적으로 고르게 실행될 수 있게 해야 함

Java Thread


  • 일반 스레드와 거의 차이가 없으며 JVM이 운영체제 역할을 함
  • 자바에는 프로세스가 존재하지 않고 스레드만 존재
    • 자바 스레드는 JVM에 의해 스케줄되는 실행 단위 코드 블록
  • 자바 스레드 스케줄링은 전적으로 JVM에 의해 이루어짐
  • JVM이 관리하는 스레드 정보
    • 스레드가 몇 개 존재하는지
    • 스레드로 실행되는 프로그램 코드의 메모리 위치는 어디인지
    • 스레드의 상태는 무엇인지
    • 스레드의 우선순위는 얼마인지
  • 즉, 개발자는 자바 스레드로 작동할 스레드 코드를 작성하고, 스레드 코드가 생명을 가지고 실행을 시작하도록 JVM에 요청하는 일 뿐

3~6. 쓰레드의 구현과 실행


쓰레드의 구현과 실행

  1. Thread 클래스를 상속
  2. Runnable 인터페이스를 구현 (다른 클래스를 상속 받을 수 있기 때문에 더 유연함)
Runnable r = new MyThread2();
Thread t2 = new Thread(r);
t2.start();

쓰레드의 실행 - start()

  • 쓰레드 생성 후 start()를 호출해야 쓰레드가 작업을 시작함
Thread t1 = new Thread(); // 쓰레드 t1을 생성
Thread t2 = new Thread(); // 쓰레드 t2을 생성
t1.start(); // 쓰레드 t1을 실행
t2.start(); // 쓰레드 t2을 실행
  • start()를 호출하면 실행 가능한 상태가 되는 것. 바로 실행되지 않음
  • OS 스케쥴러가 실행 순서를 결정. 먼저 호출되었다고 먼저 실행되지 않음

start()run()

  1. start() 가 새로운 호출 스택(Call stack)을 생성
  2. 생성된 호출 스택에서 run() 이 실행 됨

7~13. 싱글/멀티 쓰레드, I/O 블락킹


main 쓰레드

  • main 메서드의 코드를 수행하는 쓰레드
  • 쓰레드는 ‘사용자 쓰레드’ 와 ‘데몬 쓰레드(보조 쓰레드)’ 두 종류가 있음
  • 실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.

싱글쓰레드와 멀티쓰레드

  • context switching : 쓰레드의 작업 간 넘어가는 것

쓰레드의 I/O 블락킹

  • I/O 블락킹 : 입출력시 작업중단 현상
    • 멀티 쓰레드는 입출력을 기다리는 구간 동안 다른 쓰레드를 사용할 수 있어 효율적

14~17. 쓰레드의 우선순위, 그룹


쓰레드의 우선순위

  • 작업의 중요도에 따라 쓰레드의 우선순위를 다르게 하여 특정 쓰레드가 더 많은 작업시간을 갖게 할 수 있음
  • 희망사항일 뿐 OS 스케쥴러에 의해 우선순위가 정해짐
void setPriority(int newPriority) // 쓰레드의 우선순위를 지정한 값으로 변경
int  getPriority()                // 쓰레드의 우선순위를 반환
public static final int MAX_PRIORITY  = 10 // 최대우선순위
public static final int MIN_PRIORITY  = 1  // 최소우선순위
public static final int NORM_PRIORITY = 5  // 보통우선순위

쓰레드 그룹

  • 서로 관련된 쓰레드를 그룹으로 묶어서 다루기 위한 것
  • 모든 쓰레드는 반드시 하나의 쓰레드 그룹에 포함되어 있어야 함
  • 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 ‘main 쓰레드 그룹’에 속함
  • 자신을 생성한 쓰레드(부모 쓰레드)의 그룹과 우선순위를 상속받음

18~21. 데몬 쓰레드, 쓰레드의 상태


데몬 쓰레드(daemon thread)

  • 일반 쓰레드(non-daemon thread)의 작업을 돕는 보조적인 역할을 수행
  • 일반 쓰레드가 모두 종료되면 자동적으로 종료됨
  • 가비지 컬렉터, 자동저장, 화면 자동갱신 등에 사용됨
  • 무한루프와 조건문을 이용해서 실행 후 대기하다가 특정조건이 만족되면 작업을 수행하고 다시 대기하도록 작성
public void run() {
	while (true) {
		try {
			Thread.sleep(3 * 1000);
		} catch(InterruptedException e) {}
		// autoSave 값이 true면 autoSave() 호출
		if (autoSave) {
			autoSave();
		}
	}
}
// 쓰레드가 데몬인지 확인
boolean isDaemon()
// 쓰레드를 데몬 쓰레드로 변경 (반드시 start()를 호출하기 전에 실행되어야 함)
void setDaemon(boolean on)

쓰레드의 상태

상태설명
NEW쓰레드가 생성되고 아직 start()가 호출되지 않은 상태
RUNNABLE실행 중 또는 실행 가능한 상태
BLOCKED동기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태)
WAITING, TIMED_WAITING쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지상태. TIMED_WAITING은 일시정지시간이 지정된 경우를 의미
TERMINATED쓰레드의 작업이 종료된 상태

22~25. sleep(), interrupt()


sleep()

  • 현재 쓰레드(자기 자신)를 지정된 시간동안 멈추게 함
static void sleep(long millis)
static void sleep(long millis, int nanos)
  • 예외처리를 해야 함 (InterruptedException 발생 시 깨어남)
  • 특정 쓰레드를 지정해서 멈추게 하는 것은 불가능

interrupt()

  • 대기상태(WAITING)인 쓰레드를 실행대기상태(RUNNABLE)로 만듬
void interrupt()             // 쓰레드의 interrupted 상태를 false 에서 true 로 변경
boolean isInterrupted()      // 쓰레드의 interrupted 상태를 반환
static boolean interrupted() // 현재 쓰레드의 interrupted 상태를 알려주고, false 로 초기화

26~27. suspend(), resume(), stop()


suspend(), resume(), stop()

  • 쓰레드의 실행을 일시정지, 재개, 완전정지 시킴
  • 하지만, deprecated 됨. 교착상태(dead-lock)를 일으킬 가능성이 있어서 권장 X

28~29. join(), yield()


join()

  • 지정된 시간 동안 특정 쓰레드가 작업하는 것을 기다린다.
  • 예외처리 해야 함 (InterruptedException이 발생하면 작업 재개)

yield()

  • 남은 시간을 다음 쓰레드에게 양보하고, 자신(현재 쓰레드)은 실행대기
  • yield()와 interrupt()를 적절히 사용 시, 응답성과 효율성을 높일 수 있음

30~33. 쓰레드의 동기화


쓰레드의 동기화(synchronization)

  • 멀티 쓰레드 프로세스에서는 다른 쓰레드의 작업에 영향을 미칠 수 있음
  • 진행중인 작업이 다른 쓰레드에게 간섭받지 않게 하려면 ‘동기화’가 필요
  • 쓰레드의 동기화 - 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 막는 것
  • 동기화하려면 간섭받지 않아야 하는 문장들을 ‘임계 영역’으로 설정
  • 임계 영역은 락을 얻은 단 하나의 쓰레드만 출입 가능 (객체 1개에 락 1개)

synchronized를 이용한 동기화

  • synchronized로 임계영역(lock이 걸리는 영역)을 설정하는 방법 2가지
// 1. 메서드 전체를 임계 영역으로 지정
public synchronized void calcSum() {
	...
}
// 2. 특정한 영역을 임계 영역으로 지정
synchronized(객체의 참조변수) {
	...
}

34~36. wait(), notify()


wait(), notify()

  • 동기화의 효율을 높이기 위해 wait(), notify()를 사용
  • Object 클래스에 정의되어 있으며, 동기화 블록 내에서만 사용 가능
    • wait() - 객체의 lock을 풀고 쓰레드를 해당 객체의 waiting pool에 넣음
    • notify() - wating pool에서 대기중인 쓰레드 중 하나를 깨움
    • notifyAll() - wating pool에서 대기중인 모든 쓰레드를 깨움