10주차 과제: 멀티쓰레드 프로그래밍



목표

자바의 멀티쓰레드 프로그래밍에 대해 학습하세요.

학습할 것 (필수)

  • Thread 클래스와 Runnable 인터페이스
  • 쓰레드의 상태
  • 쓰레드의 우선순위
  • Main 쓰레드
  • 동기화
  • 데드락

Thread 클래스와 Runnable 인터페이스 

스레드는 Thread 클래스를 확장받는 것과, Runnable 인터페이스를 구현하는 두가지 방법으로 정의할 수 있습니다. Thread 클래스는 이미 Runnable 인터페이스를 구현한 클래스로 이 때는 Runnable 인터페이스의 run() 메서드를 구현해야 합니다. 

  • Thread 클래스를 확장하여 생성 된 각 스레드는 고유 한 객체를 만들어 해당 객체와 연관시킵니다. 한편, Runnable 인터페이스를 구현해 작성된 각 thread는 같은 실행 가능 인스턴스를 공유합니다.
  • 스레드 클래스를 확장하여 생성 될 때 각 스레드는 고유 한 객체와 연관되므로 더 많은 메모리가 필요합니다. 한편, Runnable 인터페이스를 구현해 작성된 각 thread는, 같은 오브젝트 공간을 공유하고 있기 때문에, 메모리가 적게 필요합니다.
  • Thread 클래스를 확장하면 Java가 다중 상속을 허용하지 않기 때문에 다른 클래스를 상속 할 수 없습니다. 그러나 Runnable을 구현하면 클래스가 다른 클래스를 상속 할 수있습니다.
  • Thread 클래스의 다른 메소드를 오버라이드하거나 특수화해야하는 경우에만 Thread 클래스를 확장해야합니다.
  • Thread 클래스를 확장하면 Thread 클래스의 코드와 Thread 클래스의 작업이 동일한 클래스에 포함되므로 코드에서 밀접하게 결합됩니다. 반면에, Runnable 인터페이스 구현은 스레드 코드가 스레드에 할당 된 작업에서 분리되므로 코드에서 느슨한 결합을 가져옵니다.
즉 위의 차이점을 정리하면 Runnable 인터페이스를 구현하는 방식이 좀 더 바람직하다고 볼 수 있습니다.

쓰레드의 상태

쓰레드의 상태는 자바의 Thread의 Statue 라는 static nated class 형태로 enumeration형으로 정의되어 있습니다. "Thread.State.NEW"형식으로 상태를 참조할 수 있습니다.

상태는 총 6가지가 있습니다. 

  • [객체 생성] - NEW: 새로운 쓰레드 시작 직후의 상태
  • [실행 대기] - RUNNABLE: JVM이 동작중(RUNNING). 이는 항상 동장하고 있다는 것은 아님. 리소스 획득을 위해서 잠시 대기 중
  • [일시 정지] - BLOCKED: 쓰레드가 synchronized block 혹은 method에 진입하기 위해 대기.
  • [일시 정지] - WAITING: 대기 상태로 다른 쓰레드가 작업 중. 이는 Object.wait 메소드 호출 후에도 진입되는 상태
  • [일시 정지] - TIMED_WAITING: 쓰레드가 특정 시간을 대기. 이는 Thread.sleep(), Object.wait()로 시간 인자가 들어간 메소드가 호출될때 진입되는 상태. 혹은 LockSupport, ParkNanos, LockSupport, ParkUntil 메소드도 동일
  • [실행 종료] - TERMINATED: run 메소드에서 빠져나온 경우 또는 예외가 발생하여 빠져나온 경우
[일시 정지] 상태의 3가지 BLOCKED, WAITING 그리고 TIMED_WAITING의 차이

BLOCKED 는 monitor 를 획득하기 위해 다른 쓰레드가 락을 해제하기를 기다리는 상태입니다. 잘못된 동기화를 사용하면 block 되는 쓰레드가 다수 발생하여 효율이 떨어질 수 있습니다. 반면 WAITING과 TIMED_WAITING은 wait(), sleep() 메서드 등에 의해 대기하고 있는 상태입니다. 다만 WAITING은 대기상태를 다른 메서드를 통해 해제해주어야 하는 반면 TIMED_WAITING은 시간을 인수로 가지고 있어서 일정 시간이후 대기시간이 해제됩니다.

쓰레드의 우선순위

멀티 쓰레딩 상황에서는 쓰레드 스케줄러가 우선순위에 따라 쓰레드를 특정 프로세스에 할당합니다. 이외에도 JVM이 우선 순위를 할당하거나 프로그래머가 명시적으로 순서를 부여할 수도 있습니다. 쓰레드의 우선 순위 값의 범위는 1에서 10사이이며, 우선 순위와 관련된 세가지 정적 변수는 아래와 같습니다. 

  • MAX_PRIORITY - 스레드의 최대 우선 순위이며 기본값은 10입니다.
  • NORM_PRIORITY - 스레드의 기본 우선 순위이며 기본값은 5입니다.
  • MIN_PRIORITY - 스레드가 갖는 최소 우선 순위, 기본값은 1입니다.
getPriority() 메서드를 사용해서 값으로 바인딩 된 쓰레드의 우선순위를 반환할 수 있습니다. 
setPriority() 메서드를 사용하면 주어진 쓰레드의 우선순위 값을 변경 시킬 수 있습니다. 

Main 쓰레드

자바 프로그램이 시작되면 하나의 쓰레드가 즉시 실행됩니다. 이것은 메인 프로그램이 시작될 때 실행되는 쓰레드이기 때문에 "메인쓰레드"라고 부르며 이곳은 아래와 같은 속성을 같습니다. 

  • JVM 에서 자동생성되는 쓰레드입니다. 쓰레드 생성여부에 관계없이 프로그램에 존재합니다. 
  • 다른 쓰레드가 생성되는 쓰레드입니다. 다른 쓰레드는 메인쓰레드의 자식으로 간주됩니다. 메인쓰레드의 기본 우선순위는 5로 
  • 다른 쓰레드도 이것을 상속합니다.
  • 다양한 종료작업을 수행하기 때문에 실행을 완료하는 마지막쓰레드가 됩니다. 메인쓰레드가 중지된면 프로그램도 중지됩니다.
  • Thread 클래스에 있는 currentThread()메서드를 호출하여 참조를 얻을 수 있습니다. 

동기화

멀티 쓰레딩 환경에서 여러 쓰레드가 같은 리소스에 액세스 하려 하면 간섭 및 메모리 일관성 오류가 발생할 수 있습니다.
이 때 자바의 동기화를 사용하면 간섭 및 메모리 일관성 오류 없이 여러 쓰레드를 사용할 수 있습니다.

  • 한 번에 하나의 쓰레드만 공유 변수를 읽고 쓸 수 있습니다. 다른 쓰레드는 첫 번째 쓰레드가 완료 될 때까지 기다려야하고 간섭 문제 없이 여러 쓰레드가 작동 될 수 있습니다.
  • 쓰레드가 공유 변수를 수정할 때마다 다른 쓰레드에 의한 공유 변수의 관계를 자동으로 설정합니다. 이는 한 쓰레드의 변경사항이 다른 쓰레드에 표시되게 합니다. 
  • 여러 쓰레드가 사용하는 공유 자원을 동기화 할때는 Synchronized 키워드를 사용합니다.
  • 동기화의 개념은 잠금을 기반으로 작동합니다. 쓰레드가 Java 동기화 메소드에 들어가거나 차단 될 때마다  잠금을 획득하고  실행 완료 후 잠금 을 해제합니다.

데드락 

자바에서의 데드락(Deadlock)은 흔히 "교착상태"라고 부릅니다. 이는 멀티 쓰레딩 상황에서 한 쓰레드가 특정 lock 을 놓지 않고 계속 잡고 있으면 그 락을 확보하려는 다른 쓰레드는 락이 풀릴 때까지 "대기"상태에서 기다리게 됩니다. 
위의 쓰레드의 상태에서 언급했다싶이 blocked 상태의 쓰레드가 많을 때는 데드락이 걸릴 수 있으며 이는 큰 문제를 야기합니다.

이는 1. 락의 순서를 정확하게 명시하거나, 2. 락에 대해 시간 제한을 걸어서 해결할 수 있습니다.

댓글

이 블로그의 인기 게시물

git-receive-pack not permitted on 깃 허브 로그인 관련 문제