멀티 Thread 프로그래밍 - accidentlywoo/java GitHub Wiki

멀티 Thread 프로그래밍


  1. 멀티 Tread의 개념을 이해하고, 멀티 Thread 프로그램을 구현할 수 있다.
  2. Thread 객체의 라이프 사이클을 이해하고, 동기화를 통해 Thread의 수행을 제어할 수 있다.

Thread

멀티태스킹(Multitasking)이란? 하나의 컴퓨터로 동시에 여러가지 일을 수행하는 것을 말함

우리가 사용하는 대부분의 운영체제는 멀티 프로세스를 토애서 멀티태스킹을 지원함 -> 하나의 CPU를 가지고 있는 시스템에서 실행 시간을 나누어 각 프로세스들이 CPU를 점유함

멀티 Thread의 개념

하나의 Thread를 이용하는 프로그램 -> 단일 Thread 여러 개의 Thread를 이용하는 프로그램 -> 멀티 Thread

여러 개의 Thread가 하나의 프로세스 안에서 동시에 수행되는 구조를 가짐

Thread는 프로게스가 점유한 메모리 공간에서 다른 Thread와 병렬적으로 수행됨 -> 여러 개의 프로세스로 수행되는 것에 비해 시스템 자원을 좀더 효율적으로 이용할 수 있음

public class SingleThreadTest {
	public static void main(String[] args) {
		System.out.println("싱글 Thread 프로그램 수행...");
		for(int i = 0; i < 10; i++) {
			String threadName = Thread.currentThread().getName();
			System.out.println(threadName + " : " + i);
		}
	}
}

싱글 Thread 프로그램은 프로그램이 종료될 때까지 하나의 Thread만 수행되는 프로그램임

Thread 구성 요소

가상 CPU / 수행 코드 / 처리 데이터

가상 CPU : 1개의 Thread를 수행시키기 위하여 자바 인터프리터에 의해 내부적으로 처리되는 가상 코드를 말함 수행 코드 : Thread가 구현한 기능. 즉 Thread 클래스의 run() 메서드의 코드를 말함 처리 데이터 : Thread가 처리하는 데이터를 말함

Thread 클래스

java.lang 패키지에는 Thread 기능을 구현하는데 이용되는 Thread라는 클래스가 제공됨

  • Thread 클래스의 생성자 Thread() : 기본 Thread 객체를 생성함 Thread(String name) : 특정 이름을 가진 Thread를 생성함 Thread(Runnable r) : Runnable 인터페이스를 구현한 객체를 이용하여 Thread를 생성함

  • Thread 클래스의 메서드 static void sleep(long msec) throws InterruptedException : msec에 지정된 millisecond 동안 Thread를 대기함 void start() : Thread를 시작하게 함 void join() throws InterruptedException : Thread가 끝날 때까지 대기함 void run() : Thread기능을 실행함 static void yield() : 현재 실행중인 Thread를 잠시 멈추어 다른 Thread가 실행될 여지를 줌

java.lang.Thread -> Thread가 가질 수 있는 변수와 많은 메서드가 포함되어 있음

Thread 클래스 -> 필요한 기능을 추가해서 간단하게 Thread를 생성할 수 있음

Thread 클래스를 활용해서 Thread프로그램을 작성하는 절차

  1. 특정기능을 수행하는 Thread 클래스를 작성함
class MyThread extends Thread {
  // Thread의 변수들을 선언한다.
  // run() 메서드를 Overriding 한다.
  public void run(){
    // Thread 기능을 구현한다.
  }
}
  1. Thread 클래스로부터 객체를 생성하고 생성된 Thread객체를 실행함
// Thread 클래스에 대한 객체를 생성한다.
MyThread thread = new MyThread();

// Thread를 실행한다.
thread.start();

Thread 클래스 상속

  • Thread 객체 start() 메서드를 호출할 때 동작함 Overriding한 run() 메서드가 실행됨 Thread의 실행과 무관하게 main() 메서드는 종료됨 -> main()메서드는 두 개의 Thread를 기동하는 것으로 역할을 다함 두 Thread객체가 각각 자신이 수행할 작업들을 독립적으로 수행함

Thread 클래스를 생성하는 방법

Runnable 인터페이스 상속

Thread의 수행 코드인 run() 메서드를 가지고 있어서 내부적으로는 Thread 객체를 생성해서 수행하도록 되어 있음 자바 언어에서는 다중 상속을 지원하지 않기 때문에 두 개 이상의 클래스를 상속받을 수 없음

상속받을 상위 클래스 + java.lang.Runnable(인터페이스) -> Thread 클래스

Runnable 인터페이스를 활용해서 Thread 프로그램을 작성하는 절차

  1. 필요한 Thread 기능을 수행하는 클래스를 작성함
class MyThread implements Runnable {
  // run() 메서드를 구현한다.
  public void run() {
    //Thread 기능을 작성한다.
  }
}
  1. Thread 클래스로부터 객체를 생성하고 생성된 Thread 객체를 실행함
// MyThread 객체를 생성한다.
MyThread runnable = new MyThread();

// 위의 생성된 객체를 인자로 Thread 클래스를 생성한다.
Thread thread = new Thread(runnable);

// Thread를 실행한다
thread.start()

Thread 프로그래밍

Thread의 상태도

자바 프로그램에서 사용된 Thread 객체는 생성되고 소멸될 때까지 생명 주기를 가지게 되며, Thread는 그 생명 주기에 따라 동작함

시작 --Thread 객체 생성--> 초기 상태 --Start메서드 호출--> Runnable Pool 실행가능 상태 : 대기 상태(Runnable/Not Runnable) --JVM에 의해 스케줄러가 선택--> <--Yield() 메서드 호출-- 실행 상태 : CPU를 차지하고 실행이 시작된다. run()메서드가 동작한다.

  • -- Sleep 메서드 호출-->Sleep Pool --Interrupt시간 경과 --> Runnable Pool 실행가능 상태
  • -- join 메서드 호출-->join Pool --해당 Thread 종료 Interrupt시간 경과 --> Runnable Pool 실행가능 상태
  • -- wait 메서드 호출-->Wait Pool --notify/notifyAll호출 Interrupt시간 경과 --> Sync.pool
  • -- 로크가 없는 객체의 Sync 메서드 호출-->Sync.Pool --객체의 로크 취득 -->Runnable Pool 실행가능 상태
  • -- 입출력 대기-->I/O Blocking --입출력 완료 --> Runnable Pool 실행가능 상태 --메서드 종류--> 종료 상태 --가비지 컬렉션--> 종료 : Thread가 수행할 때 할당되었던 자원은 모두 해제됨

실행 가능 상태와 스케줄러

실행 가능 상태(Runnable Pool) : 실행가능한 thread들이 모여있는 곳 --스케줄러--> 실행상태 : thread 1개

  • 스케줄러 JVM안에서 수행되는 특별한 Thread라고 생각할 수 있음 실행 상태에 있는 Thread가 CPU를 반납할 경우 여러 상황을 고려하겨 다음에 수행될 Thread 후보를 선택함

Thread의 제어 메서드

수행 가능상태(Runnable) : 대기 장소에서 JVM의 스케줄러가 Thread를 선택해서 수행 상태로 가기를 기다리는 상태임 수행 상태(Running) 수행 불가능한 상태(Not Runnable)

수행 불가능한 상태로 이동시키는 Thread 클래스의 메서드 -> sleep(), wait(), join(), yield() IO를 수행하고 있는 경우는 이미 수행되고 있는 객체에 대한 접근이 blocking 될 수 있음

Sleep() 메서드

수행 run --Sleep(milis) sleeping --시간경과--> 대기상태 Ready --> 수행 run

sleep() 메서드는 다른 Thread에게 실행 기회를 양보하기 위한 목적으로 쓰임

public void run(){
  for(int i = 0; i < 10; i++) {
     sysout(i + " " + threadName);
     try {
        Thread.sleep((int)(Math.random() * 1000));
     }catch{
     }
  }
}

join() 메서드

join()메서드는 협동 작업을 요구할 때 사용할 수 있는 메서드. 합류하길 기다린다.

수행 run --join() joining --join()이 전달된 Thread종료--> 대기상태 Ready --> 수행 run

인자로 sleep() 메서드와 같이 밀리 세컨드 단위로 표현된 시간을 줄 수 있음

이 시간이 지날 때까지 Thread가 종료하지 못할 경우 대기 중이던 Thread가 다시 실행 가능 상태로 전환됨

두 개 이상의 Thread가 존재할 때 서로 간의 직업이 시간에 따라 영향을 받거나 선후관계가 존재할 때 유용하게 사용될 수 있음

public void doWork(){
  Thread anotherThread = new Thread();
  anotherThread.start();
     try {
        //anotherThread의 수행이 끝날 때까지 기다린다.
        anotherThread.join();
     }catch{
     }
  
}

wait(), notify(), notifyAll() 메서드

Thread가 여러 개 존재하면서, 순차적 또는 일정 순서에 따라 작업하고 싶을 때 사용하는 메서드

다른 메서드들과 달리 Thread 클래스에 소속되지 않고, Object 클래스에 소속되어 있음

Synchronized 예약어와 밀접한 관련을 맺고 있음

  • wait() 메스드는 Thread를 기다리게 만드는 메서드임 수행 run --wait()--> waiting : 객체의 wait() 메서드를 실행하고 동작을 멈춘 Thread들이 저장됨 --notify()--> 대기상태 Ready --> 수행 run

JVM에 구현된 알고리즘에 따르지만 대부분 선입 선출형 큐 구조를 따르는 경우가 많음

모든 Thread를 빠져나오도록 만들고 싶다면 notifyAll() 메서드를 호출함

yield() 메서드

한 Thread가 수행 상태를 너무 오랫동안 점유하지 않고 다른 Thread에게도 기회를 주기 위해서 사용되는 메서드

수행 run --스케줄러에 의해 다시 선택된다--> <--yield()-- 대기상태 Ready

public void run() {
   for(int i = 0; i < 1000; i++) {
      for(int j = 0; j < 2000; j++) {
          sysout("연산 결과"+i*j);
          yield();
      }
   }
}

run() 메서드

run() 메서드의 내용을 모두 수행하면 Thread는 자동으로 종료하고, Thread 수행 시 할당되었던 자원은 모두 해제됨

run() 메서드를 모두 수행하기 전에 Thread를 종료할 수 있는 방법은?

stop() 메서드를 이용하는 방법 -> 단, 잘못된 동작을 유발할 가능성이 있기 때문에 deprecated 메서드로 분류됨

대기상태 Ready <--> 수행 run --> 종료

class MyThread Implements Runnable {
  private boolean flag = true;
 
  public void run() {
      while(flag) {
          try {
             Thread.sleep(1000);
          } catch {
             return;
          }
             sysout("작업을 처리한다.")
      }
  }
  
  public void threadStart() {
      Thread myThread = new Thread(this);
      myThread.start();
  }
}

Thread 스케줄링

대기 상태 --Thread 스케줄링--> 실행 상태

Thread 스케줄링을 하는 방식(인터프리터의 플랫폼에따라 조금씩 다르다)

  • 우선순위(Priority)에 따른 방식과 Round-Robin(Time Slicing)을 적용하는 방식이 있음 자바에서 생성된 모든 Thread는 기본적으로 5의 우선순위를 가짐 Thread에 할당할 수 있는 우선순위는 1에서 10까지이며 1이 가장 낮은 우선순위를 말함

  • Thread 클래스의 멤버 final static int MIN_PRIORITY : 가장 작은 우선순위 값을 나타내는 상수, 값은 1임 final static int NORM_PRIORITY : 중간 값의 우선순위를 나타내는 상수, 값은 5임 final static int MAX_PRIORITY : 가장 큰 우선순위 값을 나타내는 상수, 값은 10임 final in getPriority() : Thread의 우선순위를 반환함 final void setPriority(int p) : Thread의 우선순위를 p로 설정함

동기화의 개념

우리가 작성하는 응용 프로그램들은 많은 경우에 다수 개의 Thread가 어떤 연관 관계를 가지고 동작하게 됨

  • Thread의 동시 자원 접근 : 병렬적인 처리에 의한 자원 접근

Thread A "안녕" --출력--> Thread B "Hi" --출력--> Message.txt : "안H녕i"

-> 동기화(Synchronization)

  • Thread의 순차적 자원 접근

Thread A "안녕" --출력1-->

Message.txt : "안녕" "Hi" Thread B "Hi" <--출력2--

하나의 Thread가 수행되고 있는 동안 다른 Thread에 의해 접근되지 못하게 해야 함 파일에 대한 처리를 하는 부분은 한 순간에 하나의 Thread만 사용할 수 있는 영역이 되어야 함 -> 임계 영역

class Mailbox {
    private String message;
    public void storeMessage(String message){
       this.message = message;
    }
    public void storeMessage(String message){
       return message;
    }
}

두 개의 Thread의 의해 storeMessage()와 retrieveMessage()가 동시에 호출된다면?

  • storeMessage()가 호출되어 메시지가 저장되는 동안 retrieveMessage()가 호출되거나 또는 그 반대 상황이 발생할 수 있음
  • 전달되는 중에 있는 메시지를 가져가거나, 메시지를 가져가고 있는 중에 다른 메시지를 전달하는 경우가 생김

동기화(Synchronization)

synchronized 예약어를 통해 동기화 처리 가능하며, 두 개 이상의 Thread 가 하나의 자원을 공유하면서 작업을 진행할 때 자원을 보호하기 위해 사용함

  • synchronized 예약어 메서드 앞에 붙여서 메서드 자체를 동기화 함 여러 Thread에 의해 특정 객체의 메서드들이 동시에 호출되는 것에 대해 잠금(lock)을 설정해서 이 호출이 완료될 때까지 잠금 상태가 유지됨
class Mailbox {
    private String message;
    public synchronized void storeMessage(String message){
       this.message = message;
    }
    public synchronized void storeMessage(String message){
       return message;
    }
}

-> 동기화된 상태에서 메시지 접근이 가능함 But MailBox 클래스는 임계 영역으로 설정한 synchronized 메서드가 각 Thread에 의해 번갈아 교대로 호출되어야 함 -> synchronized 메서드 구현만으로는 부족함

Thread 사이의 통신

두 개 이상의 Thread가 서로 협력하며 공유 자원을 사용하도록 하는 것

수행중인Thread --wait() 메서드 호출--> 대시 상태

다른 Thread --notify() 메서드 호출--> 실행 가능 상태

  • wait() 메서드

  • notify() 메서드

  • notifyAll() 메서드 빠져 나오는 Thread가 무엇인가를 가정하고 코드를 작성하는 것은 매우 위험한 상황을 발생시킬 수 있음 --> 모든 Thread를 Wait Pool에서 꺼내어 놓고 같은 조건에서 다시 시작하도록 작성하는 것이 더욱 안전할 수도 있음

두 개 이상의 Thread일 경우에는 wait()와 notifyAll()을 사용하는 편이 안전함