본 게시글은 대학 전공수업을 들으며 노션에 정리한 내용을 블로그로 옮긴 것으로, 노션 웹을 통해 최적화된 형태로 읽으시길 권장드립니다.(➡️ 노션 링크)

13강 멀티 스레드 프로그래밍

프로세스와 스레드

Java 프로그램은 하나의 프로세스로 만들어져 실행된다.

  • 프로세스 : 프로그램의 통상적 실행 단위로, 자원을 확보하고 있는 ‘실행 중인 프로그램’

지금까지는 1프로세스 1스레드, 1개 스레드에서 main()가 호출되며 실행됨(단일스레드)

  • 스레드 : 프로세스 내에 있는 소규모 실행 흐름으로, ‘경량 프로세스’

멀티스레드 : 하나의 프로세스 내에서 여러 스레드가 동시 실행.

멀티스레드

: 하나의 프로세스 내에서 동시 실행되는 여러 스레드

멀티스레드를 사용할 경우, Main스레드에서 자식 스레드를 만들며 여러 스레드가 동시에 독립적으로 실행되고 종료될 수 있다.

Thread 클래스

  • 스레드의 생성과 관리를 위한 메소드 제공
  • 스레드 생성을 위해 Thread 클래스 객체가 필요함
  • 주요 생성자
생성자 설명
Thread() 스레드 생성. 이름은 자동으로 ‘Thread-n’으로 주어짐
Thread(String name) 이름을 name으로 지정해 생성
Thread(Runnable target) 스레드 생성, 실행되며 target객체의 run()이 실행 (이름은 자동)
Thread(Runnable target, String name) 위와 동일하나 이름을 name으로 지정
static Thread currentThread() 실행중인 스레드 객체의 참조값 리턴
String getName() this 스레드 이름 리턴
void setNameI(String name) this 스레드 이름 name으로 변경
int getPriority() this 스레드 우선순위 리턴
void setPriority(int newPriority) this 스레드 우선순위를 newPriority로 변경
void start() run() 호출 → this 스레드 실행

스레드 생성과 실행

  1. Thread 유형 객체 t 생성
  2. t.start() 호출
    • 스레드 실행 시작 : run()메소드 호출

실행방법 1 : Thread 클래스를 상속받는 클래스

  • Thread0과 1은 독립적으로 실행되기때문에 출력 순서는 달라질 수 있다.
  • Thread 클래스를 상속받는 MyThread 클래스를 생성 후 start()
package lecture13;

class MyThread extends Thread {
    //run() '재정의' : this스레드 이름 리턴
  public void run() {
    for (int i = 0; i < 10; i++) {
      System.out.println(getName());
    }
  }
}

public class ThreadEx1 {
  public static void main(String args[]) {
    Thread t1 = new MyThread();
    t1.start();
    Thread t2 = new MyThread();
    t2.start();
    System.out.println("main");
  }
}

실행 결과(1)

실행방법 2 : Runnable 인터페이스 구현

  • Thread객체를 생성할 때, Runnable객체를 인자로 사용함.

  • 따라서 정의된 MyThread 클래스는 Thread가 아님!

    ⇒ Thread 객체로 생성한 방법1과 비교해, 더 유연한 스레드 실행 방식.

package lecture13;

class MyThread2 implements Runnable {
    //run() '구현' : 실행중인 스레드 객체 참조값 리턴
  public void run() {
    for (int i = 0; i < 10; i++) {
        //
      System.out.println(Thread.currentThread().getName());
    }
  }
}

public class ThreadEx2 {

  public static void main(String[] args) {
    Thread t1 = new Thread(new MyThread2(), "스레드 0");
    t1.start();
    Thread t2 = new Thread(new MyThread2(), "스레드 1");
    t2.start();
    System.out.println("main");
  }
}

실행 결과(2)

멀티스레드 실행

각 스레드는 정해진 순서 없이 독립적으로 실행됨

⇒ 실행 결과는 매번 다를 수 있으며, 예측할 수 없음.

main스레드는 다른 스레드를 시작시킴. 마찬가지로 다른 스레드와 독립적으로 실행/종료됨.

스레드 제어하기

스레드 상태

: 생성된 스레드는 CPU를 얻어 실행되고, 종료될 때 까지 여러 상태 변화를 겪음

상태 설명
Startable 객체 생성 / start() 실행 전
Runnable start() 호출 됨 / CPU 획득 전
Running CPU 얻어 실행 중
NotRunning 실행 중이였다가 CPU 잃고 중단 / Blocked, Waiting, Timed-Waiting
Dead run() 메소드 종료

Thread State Transition

source: https://www.javatpoint.com/thread-states-in-java

스레드의 상태제어 메서드

void setPriority(int newPriority)

  • 스레드 우선순위 변경 : 우선순위가 높은 스레드가 CPU를 얻을 확률이 높음

static void sleep((long millis) throws InterruptedException

  • millis 시간만큼 실행을 멈추고 NotRunning 상태로 들어감
  • 다른 스레드가 interrupt() 메서드로 꺠우면, InterruptedException예외가 발생

static void yield()

  • 잠시 실행을 멈추고 Runnable로 들어감 → 다른 스레드에게 양보

void join() throws InterruptedException

  • 종료될 때 까지 기다림
  • 실행중이였던 스레드는 Not Running으로 들어감.
  • 다른 스레드가 깨워주면 예외가 터지며 리턴

void interrupt()

  • 스레드를 인터럽트 시켜 Runnable 상태로 바꿈.
  • 스레드가 중단된 상태(wait() / join() / sleep()에 의해)였을 때 사용됨

void wait() throws InterruptedException

  • 스레드가 대기상태로 감
  • 다른 스레드가 이 객체를 notify() 호출할 때 까지 기다림.

void notify()

  • wait()을 호출 → 대기중인 스레드 깨워줌.
    • synchronized 메소드의 내부에서만 호출 가능.

더 자세한 내용은 공부하지 말라고 하신다…(굿)

  • 예시코드

      package lecture13;
    
      class MyThread3 implements Runnable {
    
        static int count = 0;
    
        public void run() {
          for (int i = 0; i < 50; i++) {
            String thd_name = Thread.currentThread().getName();
            System.out.print(thd_name + " ");
    
            //thd1이면 Runnable로 들어가 멈추고 다른 스레드에게 양보.
            //(단, 반드시 양보하는 것은 아님)
            if (thd_name.equals("thd1")) {
              Thread.yield();
            }
    
            count++;
            if (count % 10 == 0) {
              System.out.println();
            }
          }
        }
      }
    
      public class ThreadYield {
        public static void main(String args[]) {
          Thread my_thread1 = new Thread(new MyThread3(), "thd1");
          Thread my_thread2 = new Thread(new MyThread3(), "thd2");
          my_thread1.start();
          my_thread2.start();
        }
      }

    1이 먼저 출력 중 → 1보고 양보하라고 함 → 2가 먼저 출력된 후 1이 출력 됨.

    1이 먼저 출력 중 → 1보고 양보하라고 함 → 2가 먼저 출력된 후 1이 출력 됨.

    join()으로 제어하기

    interrupt()로 제어하기

    스레드간 간섭(Thread Interference)

    여러개의 스레드가 하나의 공유 객체에 동시 접근하는 경우 데이터 무결성이 깨짐

    +1, -1을 하면 0이 되어야 하는데 간섭으로 인해 최종 값이 -1이 되버림.

    +1, -1을 하면 0이 되어야 하는데 간섭으로 인해 최종 값이 -1이 되버림.

    스레드 동기화(Synchronized)

    서로 다른 스레드들이 공유 자원을 다룰 때 데이터 무결성을 보장하는 것

    한번에 오직 한개의 스레드만이 공유 객체에 접근하도록 동기화 하는 것(상호 배제)

    사용방법 2가지

    • 상호배제원칙

    • 키워드 synchronized

      동기화 메소드 또는 블록을 제공

      수정할 때 다른 스레드에서 같은 코드를 수행할 수 없게 함.

synchronized 메소드

: 스레드 동기화를 위한 키워드 → 한번의 하나에 스레드에 의해서만 실행

  • 이 메소드를 실행하려면, 메소드를 호출한 객체에 대한 lock을 얻어야 함.
  • 일부 블록만 동기화 하는것도 가능
    • synchronized(객체) {…}
      • 이때의 객체는 공유자원으로 대개 this를 사용

synchronized 메소드