본문 바로가기
☕ Java 웹 프로그래밍/Java

[프로그래머스] Java(자바) 중급 | Part 7. 스레드

by 일단연 2023. 5. 18.

 스레드란? 

운영체제(Operating System)

  • 컴퓨터의 하드웨어를 사용하게 해주는 프로그램

 

프로세스(Process)

  • 현재 실행되고 있는 프로그램
  • 즉, 사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행 중인 것
  • 프로세스는 프로그램에 사용되는 데이터와 메모리 등의 자원 그리고 스레드로 구성됨
  • Java 프로그램은 JVM에 의해 실행됨 > JVM도 프로그램 중 하나
  • 운영체제 입장에서 보면, Java도 하나의 프로세스로 실행을 하는 것
  • 워드프로세서가 하나의 프로세스라면, 하나의 프로세스 안에서도 여러 개의 흐름이 동작할 수 있는데, 이것을 Thread라고 부름

 

스레드(Thread)

  • 동시에 여러 가지 작업을 동시에 수행할 수 있게 하는 것
  • 프로세스(process) 내에서 실제로 작업을 수행하는 주체
  • 모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행
  • 멀티스레드 프로세스(multi-threaded process): 두 개 이상의 스레드를 가지는 프로세스
  • Java는 JVM만 있다면 어떤 운영체제에서든지 동작함
  • Java 프로그램 안에서 여러 개의 흐름이 동작할 수 있도록 하려면 스레드를 공부해야 함

 

 스레드 만들기 

Java에서 Thread를 생성하는 방법

  • Thread 클래스를 상속받는 방법
    • Thread 클래스를 상속받아 생성한 Thread 클래스는 Thread임
    • run( )메소드를 오버라이딩해줘야 함
  • Runnable 인터페이스를 구현하는 방법
    • Runnable 인터페이스를 구현해 생성한 Thread 클래스는 Thread가 아님
    • Runnable 인터페이스는 몸체가 없는 메소드인 run() 메소드 단 하나만을 가지는 간단한 인터페이스
      • Runnable 인터페이스가 이미 run( )메소드를 갖고 있어서 따로 오버라이딩하지 않아도 됨
      • start( )메소드가 없기 때문에 Thread 객체를 따로 생성해야 함
        • Thread의 생성자는 Runnable을 받아들일 수 있음
        • 그 객체의 생성자 안에 미리 생성해둔, Runnable 인터페이스를 구현한 클래스의 객체를 넣기
  • 두 방법 모두 스레드를 통해 작업하고 싶은 내용을 run( )메소드에 작성하면 됨
    • run( )메소드: 수행흐름이 하나 더 생겼을 때 흐름을 가지는 메소드 (다른 흐름의 main( )메소드라고 할 수 있음)
  • Thread를 동작시킬 땐 run( )메소드가 아닌 start( )를 호출해야 함
    • start( )메소드
      • Thread가 실행할 준비를 하게 해주며, Thread가 실행할 준비가 다 되면 내부적으로 run( )메소드를 호출함
      • start( )메소드를 호출하지 않으면 Thread는 동작하지 않음

 

Java가 Runnable 인터페이스를 구현해 Thread를 생성하는 방법을 제공하는 이유

  • 클래스에는 단일상속만을 지원 (클래스 다중상속 불가능)
  • But, 인터페이스는 다중상속 가능
  • 이미 다른 클래스를 상속받고 있는 경우
    • 또 Thread 클래스를 상속받을 수 X
    • 이럴 때도 Thread가 꼭 필요하다면, Runnable 인터페이스를 사용하면 됨

 

1. Thread를 상속받아 스레드를 생성하는 방법

Thread 클래스를 상속받은 MyThread1 클래스

  • 1) java.lang.Thread클래스를 상속받음 (클래스 생성할 때 수퍼클래스 지정해줘야 함)

  • 2) Thread가 가지고 있는 run( )메소드를 오버라이딩
    • Source 탭 > Override/Implement Methods 클릭 > run( ) 메소드 클릭

  • 3) String 타입의 필드 선언
  • 4) 처음 객체가 생성될 때부터 String 값을 가지도록 매개변수 타입이 String인 생성자 생성
  • 5) run( )메소드 안에서 10번 반복하면서 str을 출력
    • Thread 속도가 너무 빨라서 sleep( )메소드를 이용해 속도를 늦춰줌
      • sleep( )메소드: Thread클래스의 메소드로, 매개변수로 long 타입의 millis를 받으며 해당 millis(밀리초)만큼 쉬어가게 함
    • 랜덤하게 쉬어갈 수 있도록 Math클래스의 random( )메소드를 이용
      • random( )메소드: Math클래스의 메소드로, 0.0 ~ 1.0 사이의 double 값을 랜덤하게 리턴
    • Math.random( )*1000 값을 int로 명시적 형변환
    • sleep( )메소드가 CheckedException을 발생시키기 때문에 try-catch문으로 Exception 처리해줘야 함
public class MyThread1 extends Thread {
  String str;
  public MyThread1(String str){
    this.str = str;
  }
  
  @Override
  public void run(){
    for(int i = 0; i < 10; i ++){
      System.out.print(str);
      try {
        //컴퓨터가 너무 빠르기 때문에 수행결과를 잘 확인 할 수 없음
        //Thread.sleep() 메서드를 이용해 조금씩 쉬었다가 출력할 수 있게 함
        Thread.sleep((int)(Math.random() * 1000));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    } 
  } 
}

Thread 클래스를 상속받은 MyThread1을 사용하는 ThreadExam1 클래스

  • 1) 스레드 객체 2개 생성
  • 2) Thread 클래스가 가지고 있는 start( )메소드를 호출
    • Thread를 동작시킬 땐 run( )메소드가 아닌 start( )를 호출해야 함
  • Thread를 상속받았으니까 MyThread1은 Thread임
  • t1.start( ); 를 실행할 때 프로그램의 흐름이 2개로 나뉨
    • main( )메소드를 수행하는 흐름과, Thread가 하나 더 발생돼서 run( )메소드가 실행되는 흐름
  • t2.start( ): 를 실행시키면 프로그램의 흐름이 3개로 바뀜
    • main( )메소드를 수행하는 흐름과, Thread가 하나 더 발생돼서 run( )메소드가 실행되는 흐름, 또 Thread가 하나 더 발생돼서 run( )메소드가 실행되는 흐름
  • Thread가 준비되는 데에 시간이 좀 걸리기 때문에 main 스레드가 먼저 종료됨
    • But, main 스레드가 종료됐다 해서 프로그램이 종료되는 게 아님
    • 다른 스레드들이 모두 종료돼야 프로그램도 종료됨
public class ThreadExam1 {
  public static void main(String[] args) {
    // MyThread 인스턴스를 2개 생성
    MyThread1 t1 = new MyThread1("*");
    MyThread1 t2 = new MyThread1("-");
스
    t1.start();                    //2. 스레드
    t2.start();                    //3. 스레드
    System.out.print("main end");  //1. main 스레드
  }   
}
  • 결과
    • sleep( )메소드로 랜덤하게 밀리초를 줬기 때문에 t1과 t2의 결과가 섞여서 나옴
    • main end
      -
      *
      *
      -
      *
      -
      *
      *
      *
      -
      *
      -
      *
      -
      *
      *
      -
      -
      -
      -

 

2. Runnable 인터페이스를 구현해 스레드를 생성하는 방법

Runnable 인터페이스를 구현한 MyThread2 클래스

  • 1) java.lang.Runnable클래스를 구현 (클래스 생성할 때 인터페이스를 추가해줘야 함)

  • 2) String 타입의 필드 선언
  • 3) 처음 객체가 생성될 때부터 String 값을 가지도록 매개변수 타입이 String인 생성자 생성
  • 4) run( )메소드 안에서 10번 반복하면서 str을 출력
    • Runnable 인터페이스는 이미 run( )메소드를 갖고 있기 때문에 따로 run( )메소드를 오버라이딩해주지 않아도 MyThread2 클래스를 생성하면서 자동으로 run( ) 메소드가 생성됨
    • Thread 속도가 너무 빨라서 sleep( )메소드를 이용해 속도를 늦춰줌
      • sleep( )메소드: Thread클래스의 메소드로, 매개변수로 long 타입의 millis를 받으며 해당 millis(밀리초)만큼 쉬어가게 함
    • 랜덤하게 쉬어갈 수 있도록 Math클래스의 random( )메소드를 이용
      • random( )메소드: Math클래스의 메소드로, 0.0 ~ 1.0 사이의 double 값을 랜덤하게 리턴
    • Math*random( )*1000 r값을 int로 명시적 형변환
    • sleep( )메소드가 CheckedException을 발생시키기 때문에 try-catch문으로 Exception 처리해줘야 함
public class MyThread2 implements Runnable {
  String str;
  public MyThread2(String str){
    this.str = str;
  }

  @Override
  public void run(){
    for(int i = 0; i < 10; i ++){
      System.out.print(str);
      try {
        Thread.sleep((int)(Math.random() * 1000));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    } 
  } 
}

Runnable 인터페이스를 구현한 MyThread2을 사용하는 ThreadExam2 클래스

  • MyThread2는 Thread를 상속받지 않았기 때문에 Thread가 아님
  • Thread 객체를 따로 생성해줘야 함
    • 해당 생성자에 MyThread2 객체를 넣어서 Thread를 생성
  • Thread 클래스가 가진 start( )메소드를 호출
public class ThreadExam2 {  
  public static void main(String[] args) {
    MyThread2 r1 = new MyThread2("*");
    MyThread2 r2 = new MyThread2("-");
    
    Thread t1 = new Thread(r1);
    Thread t2 = new Thread(r2);

    t1.start();
    t2.start();
    System.out.print("!!!!!");  
  }   
}
  • 결과
    • sleep( )메소드로 랜덤하게 밀리초를 줬기 때문에 t1과 t2의 결과가 섞여서 나옴
    • main end
      -
      *
      -
      *
      -
      *
      -
      *
      -
      -
      *
      -
      *
      -
      -
      *
      -
      *
      *
      *

 

실습

  • 문제 설명
    • 자바에서는 두 개의 클래스로부터 상속을 받을 수 없으므로 이미 상속을 받은 클래스가 스레드로 동작하려면 Runnable인터페이스를 구현해야 합니다. Bus클래스는 Car를 상속받은 상태인데요. Bus클래스가 스레드로 동작할 수 있게 Runnable인터페이스를 구현하고 run메소드를 오버라이드 해 보세요.
  • 해설
    • Car 클래스를 상속받은 Bus가 Runnable 인터페이스를 상속받게 함
    • Bus 기본생성자 생성
    • run( )메소드에서 Car의 drive( )메소드 오버라이딩
  • Bus 클래스
public class Bus extends Car implements Runnable {
  //이미 Car를 상속받은 상태이기 때문에 Thread를 상속받을 수는 없습니다.
  //Runnable인터페이스를 implements하고 run메소드를 작성해 보세요.
  public Bus(){
        
  }
    
  @Override
  public void run(){
    System.out.println("버스가 달립니다.");
  }
}
  • Car 클래스
public class Car{
  public String name;
  public int number;
    
  public void drive(){
    System.out.println("차가 달립니다.");
  }
}
  • RunnableExam 클래스
public class RunnableExam{
  public static void main(String [] argv){
    Bus bus = new Bus();
    /*
    Runnable을 구현한 클래스를 이용해 스레드를 이용할 때는
    아래와 같이 Thread의 생성자에 해당 객체(bus)를 전달하면 됩니다.
    하지만 이대로 실행하면 에러가 발생합니다.
    bus가 runnable을 implements하고 있지 않기 때문입니다.
    */
    Thread busThread = new Thread(bus);
    busThread.start();
  }
}


 

 스레드와 공유객체 

공유객체

  • 여러 개의 Thread가 함께 사용하는 하나의 객체
    • 하나의 객체를 여러 개의 Thread가 가지고 있다
    • 예: 여러 명의 아이들이 하나의 그네를 함께 사용하는 것

 

공유객체 예시

  • MusicBox라는 클래스가 있다고 가정
    • 해당 클래스는 3개의 메소드를 가짐
    • 각각의 메소드는 1초 이하의 시간 동안 10번 반복하면서 어떤 음악을 출력
  • 이러한 MusicBox를 사용하는 MusicPlayer 3명 생성
    • MusicPlayer 3명은 하나의 MusicBox를 사용
  • MusicBox는 공유객체, MusicPlayer는 Thread 클래스를 상속받는 Thread

 

공유객체 MusicBox 클래스

  • 각각 다른 메시지를 출력하는 3개의 메소드를 구현
    • playMusicA( )메소드: 신나는 음악
    • playMusicB( )메소드: 슬픈 음악
    • playMusicC( )메소드: 카페 음악
public class MusicBox { 
  *//메시지 '신나는 음악'이 1초 이하로 쉬면서 10번 반복 출력되는* playMusicA()*메소드* 
  public void playMusicA(){
    for(int i = 0; i < 10; i ++){
      System.out.println("신나는 음악");
      try {
        Thread.sleep((int)(Math.random() * 1000));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    } *// for*
  } *//playMusicA*

  *//메시지 '슬픈 음악'이 1초 이하로 쉬면서 10번 반복 출력되는* playMusicB()*메소드* 
  public void playMusicB(){
    for(int i = 0; i < 10; i ++){
      System.out.println("슬픈 음악");
      try {
        Thread.sleep((int)(Math.random() * 1000));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    } *// for*
  } *//playMusicB*

  *//메시지 '카페 음악'이 1초 이하로 쉬면서 10번 반복 출력되는* playMusicC()*메소드* 
  public void playMusicC(){
    for(int i = 0; i < 10; i ++){
      System.out.println("카페 음악");
      try {
        Thread.sleep((int)(Math.random() * 1000));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    } *// for*
  }*//playMusicC*
}

MusicBox를 가지는 Thread객체 MusicPlayer 클래스

  • MusicPlayer 클래스는 Thread 클래스를 상속받는 Thread
  • 1) 변수 선언
    • Music의 타입을 받아들일 수 있는 int형 변수 type
    • 공유객체 MusicBox를 타입으로 가지는 변수 musicBox
  • 2) 생성자 생성
    • 처음부터 type과 musicBox를 매개변수로 받아들이는 생성자
  • 3) run( )메소드 오버라이딩
    • Source 탭 > Override/Implement Methods > run( ) 메소드 클릭
    • MusicBox가 가지고 있는 3개의 메소드를 type에 따라 다르게 호출하기 위해 switch문 사용해 반복
public class MusicPlayer extends Thread{
  int type;
  MusicBox musicBox;  

  // 생성자로부터 musicBox와 정수를 하나 받아들여서 필드를 초기화
  public MusicPlayer(int type, MusicBox musicBox){
    this.type = type;
    this.musicBox = musicBox;
  }       

  // type이 무엇이냐에 따라서 musicBox가 가지고 있는 메소드가 다르게 호출
  public void run(){
    switch(type){
      case 1 : musicBox.playMusicA(); break;
      case 2 : musicBox.playMusicB(); break;
      case 3 : musicBox.playMusicC(); break;
    }
  }       
}

MusicBox와 MusicPlayer를 이용하는 MusicBoxExam1 클래스

  • 1) MusicBox 인스턴스 생성
  • 2) MusicPlayer 인스턴스 생성
    • 생성자에는 type 값과 MusicBox 인스턴스인 box 넣기
  • 3) 스레드 MusicPlayer의 인스턴스를 이용해 Thread 클래스가 가진 start( )메소드를 호출
public class MusicBoxExam1 {

  public static void main(String[] args) {
    // MusicBox 인스턴스 생성
    MusicBox box = new MusicBox();

    MusicPlayer kim = new MusicPlayer(1, box);
    MusicPlayer lee = new MusicPlayer(2, box);
    MusicPlayer kang = new MusicPlayer(3, box);

    // MusicPlayer스레드를 실행
    kim.start();
    lee.start();
    kang.start();           
  }   
}

결과

  • 스레드 MusicPlayer에 따라 공유객체 MusicBox의 메소드가 각각 다르게 호출됨
    • 신나는 음악
      카페 음악
      슬픈 음악
      슬픈 음악
      카페 음악
      신나는 음악
      카페 음악
      카페 음악
      슬픈 음악
      슬픈 음악
      카페 음악
      신나는 음악
      카페 음악
      슬픈 음악
      슬픈 음악
      슬픈 음악
      카페 음악
      카페 음악
      카페 음악
      신나는 음악
      신나는 음악
      신나는 음악
      슬픈 음악
      신나는 음악
      슬픈 음악
      카페 음악
      슬픈 음악
      신나는 음악
      신나는 음악
      신나는 음악

 

 동기화 메소드와 동기화 블록 

동기화 메소드

  • 공유객체가 가진 메소드를 동시에 호출되지 않도록 하는 방법
  • 여러 개의 Thread들이 공유객체의 메소드를 사용할 때, 메소드에 synchronized가 붙어 있을 경우 먼저 호출된 메소드가 객체의 사용권(Monitoring Lock)을 얻음
    • 메소드 앞에 synchronized를 붙여 실행하면, 해당 메소드가 모두 실행된 후에 다음 메소드가 실행됨
    • 해당 Monitoring Lock은 메소드 실행이 종료되거나, wait( )와 같은 메소드를 만나기 전까지 유지됨
    • 다른 스레드들은 Monitoring Lock을 놓을 때까지 대기
  • synchronized를 붙이지 않은 메소드
    • Thread들이 동시에 호출하더라도 객체를 망가뜨리지 않는 메소드
    • 다른 스레드들이 synchronized 메소드를 실행하면서 Monitoring Lock을 획득했다 하더라도, 그것과 상관없이 실행됨 (synchronized 메소드들 사이사이에 출력됨)
  • 문법
    • 접근제한자 synchronized 리턴타입 메소드명( ){ }
      • 메소드 앞에 synchronized를 붙임
  • 예시
    • playMusicA( )메소드 앞에 synchronized가 붙었기 때문에 객체의 사용권(Monitoring Lock을 가짐
    • playMusicA( )메소드가 실행된 후에 다른 메소드들이 실행됨
public synchronized void playMusicA(){
  for(int i = 0; i < 10; i ++){
    System.out.println("신나는 음악!!!");
    try {
      Thread.sleep((int)(Math.random() * 1000));
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  } *// for* 
} *//playMusicA*

 

동기화 블록

  • 마지막에 대기하는 스레드가 너무 오래 기다리는 것을 막기 위한 방법
    • synchronized를 메소드에 붙여 사용할 경우, 메소드의 코드가 길어지면 마지막에 대기하는 스레드가 너무 오래 기다리게 됨
    • 이를 막기 위해 메소드에 synchronized를 붙이지 않고, 문제가 있을 것 같은 부분(동시에 실행되면 안 되는 부분)에만 synchronized블록을 사용
  • synchronized(this){ }
    • 현재 객체가 Monitoring Lock을 가질 수 있을 경우에 Monitoring Lock을 가지게 해서 동기화하겠다는 의미
    • this는 객체 자신을 의미함
    • 예시에서, playMusicA가 종료될 때 Monitoring Lock을 놓게 되는데, playMusicC의 synchronized 블록에서 대기하던 스레드가 이때 실행됨
      • 딱 한 줄만 블록으로 감싸져 있으니까 그때만 Monitoring Lock을 가지고 실행하게 되니, 그 이외의 다른 스레드들이 조금 더 빠르게 진입할 수 있게 됨
public void playMusicC(){
  for(int i = 0; i < 10; i ++){
    synchronized(this){
      System.out.println("슬픈 음악!!!");
    }
    try {
      Thread.sleep((int)(Math.random() * 1000));
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  } *// for*
} *//playMusicB*

 

 스레드와 상태제어 

  • 스레드가 3개가 있다면 JVM은 시간을 잘게 쪼갠 후에 한번은 스레드1을, 한번은 스레드 2를, 한번은 스레드 3을 실행
  • 이것들이 빠르게 일어나다 보니 스레드가 모두 동작하는 것처럼 보임
  • 스레드는 실행되었다가 멈췄다가 하는 것을 반복
  • But, 멈췄다고 해서 아주 멈춘 것은 아니고 실행대기 상태인 것

 

스레드의 상태

  • new로 스레드 객체를 생성하고 start( )메소드를 호출하면 스레드는 실행대기(RUNNABLE) 상태가 됨
    • 실행대기 상태: 아직 스케줄링이 되지 않아 실행을 기다리는 상태
  • 실행대기 상태에 있는 스레드 중 스레드 스케줄링으로 선택된 스레드가 CPU를 점유하고 run( )메소드를 실행하면, 실행(RUNNING) 상태가 됨
  • 실행 상태의 스레드는 run( )메소드를 모두 실행하기 전에 스레드 스케줄링에 의해 다시 실행대기 상태로 돌아갈 수 있으며, 그렇게 되면 실행대기 상태에 있는 다른 스레드가 선택되어 실행 상태가 됨
  • 스레드는 실행대기(RUNNABLE) 상태와 실행(RUNNING) 상태를 번갈아가며 자신의 run( )메소드를 조금씩 실행함
  • 실행 상태에서 run( )메소드가 종료되면 더이상 실행할 코드가 없기 때문에 스레드의 실행이 멈추고 스레드는 종료(TERMINATED) 상태가 됨

 

  • 스레드가 실행 상태에서 실행대기 상태로 가지 않고, 일시정지 상태로 가기도 함
    • 일시정지 상태: 스레드가 실행할 수 없는 상태
    • 일시정지 상태의 종류; WAITING, TIME_WAITING, BLOCKED
  • 일시정지 상태의 스레드가 다시 실행 상태로 가기 위해서는 다시 실행대기 상태로 가야 함

 

  • 자바 5부터 Thread 클래스에 getState( )메소드로 스레드의 상태를 코드로 확인할 수 있음
    • Thread 클래스를 상속받아 구성한 경우에는 Thread.currentThread( ).getState( )도 가능하고, 부모의 getState( )메소드를 바로 이용해도 됨
  • getState( )메소드: 스레드 상태에 따라 Thread.State 열거 상수를 리턴
  • Thread.State의 내부에는 6개의 열거 상수(NEW, RUNNABLE, WAITING, TIMED_WAITING, BLOCKED, TERMINATED)가 저장되어 있음
public enum State {
  NEW,
  RUNNABLE,
  WAITING,
  TIMED_WAITING,
  BLOCKED,
  TERMINATED;
}
상태 열거 상수 설명  
객체 생성 NEW new 키워드로 스레드의 객체가 생성된 상태 (start( )메소드 호출 전)  
실행 대기 RUNNABLE start( )메소드 이후 CPU를 사용할 수 있는 상태  
일시 정지 WAITING 다른 스레드가 통지할 때까지 기다리는 상태 (한없이 일시정지)  
TIMED_WAITING ° 일정시간 동안 일시정지된 상태
° 일정시간이 지나거나 중간에 interrupt( )메소드가 호출되면 다시 RUNNABLE상태가 됨
 
BLOCKED ° 동기화 메소드/블록을 실행하기 위해 먼저 실행 중인 스레드의 실행 완료를 기다리는 동안 차단(Blocked)된 상태
   = 먼저 실행 중인 스레드가 Monitoring Lock를 반납할 때까지 기다림
° 스레드의 동기화 영역 수행이 완료되면 Blocked상태의 스레드는 Runnable상태가 되어 해당 동기화 영역을 실행
 
종료 TERMINATED ° run( )메소드의 작업 내용이 모두 완료되어 스레드가 종료된 상태
° 한 번 실행(start( )메소드 호출)된 스레드는 재실행이 불가능하며 객체를 새롭게 생성해야 함
 

 

스레드의 상태를 제어하는 메소드

 Thread의 start( )메소드 

  • 스레드를 실행시킴

 

 Thread의 sleep( )메소드 

  • 형태: sleep(long millis) / sleep(long millis, int nanos)
  • 현재 실행 중(RUNNING 상태)인 스레드를 일정 시간 동안 일시정지시킴(TIME_WAITING 상태로 전이됨)
  • 일정 시간이 끝나거나 interrupt( )메소드가 호출될 경우 RUNNABLE 상태로 전이됨
    • 그러므로 InterruptException을 Exception 처리해줘야 함

 

 Object의 wait( )메소드 

  • Object의 wait( )메소드
  • 동기화(synchronized) 블록 내에서 스레드를 일시정지 상태로 만듦
  • 매개값으로 주어진 시간이 지나면 자동으로 실행대기 상태가 됨
  • 시간이 주어지지 않으면 notify( ), notifyAll( ) 메소드에 의해 실행대기 상태로 갈 수 있음
  • wait( )메소드가 호출되면 객체의 사용권(Monitoring Lock)을 놓게 됨 > 대기 중인 다른 메소드가 실행됨
    • 공유객체의 동기화 블록이 실행되었는데 이미 다른 스레드가 Monitoring Lock을 획득한 상태라면 Lock pool에서 일시정지 상태가 됨
  • wait( )메소드는 InterruptedException를 발생시키므로 Exception 처리를 해줘야 함

 

 Object의 notify( ), notifyAll( )메소드 

  • 동기화(synchronized) 블록 내에서 wait( )메소드에 의해 일시정지 상태에 있는 스레드를 실행대기 상태로 만듦

 

 Thread의 interrupt( )메소드 

  • Thread의 interrupt( )메소드
  • 일시정지 상태의 스레드에 InterruptedException 예외를 발생시켜, 예외 처리 코드(catch)에서 실행대기상태로 가거나 종료상태로 갈 수 있게 함
  • 스레드의 실행을 중단할지는 해당 스레드의 책임

 

 Thread의 run( )메소드 

  • Thread의 start( )메소드를 호출해 그 안의 run( )메소드가 종료되면 스레드도 종료됨(Dead상태)

 

 Thread의 yield( )메소드 

  • RUNNING 상태의 스레드가, 우선순위가 동일한 다른 스레드에게 실행을 양보하고 RUNNABLE 상태로 전이됨
    • 다른 스레드에게 실행을 양보 = 다른 스레드가 좀 더 빠르게 실행되게 함
    • 다른 스레드가 Runnable상태에서 Running 상태가 됨

 

 Thread의 join( )메소드 

  • 형태: join( ), join(long millis), join(long millis, int nanos) )
  • 현재 스레드를 일시정지시킨 후 특정 스레드가 일정시간 동안 실행되도록 함
    • 시간을 명시할 경우, 현재 스레드는 TIME_WAITING 상태로 전이됨
    • 시간을 명시하지 않을 경우, 특정 스레드가 종료될 때까지 현재 스레드는 WAITING 상태로 전이됨 (특정 스레드가 종료되면 RUNNABLE 상태로 전이됨)
    • RUNNABLE 상태로 가려면 join( )메소드를 멤버로 가지는 스레드가 종료되거나, 매개값으로 주어진 시간이 지나야 함

 

 deprecated된 메소드: 더이상 사용할 수 X 

  • suspend( ): Runnable/Running상태의 스레드를 Blocked상태로 만듦 > wait( )로 대체
  • stop( ): 스레드를 종료시킴
  • resume( ): Blocked상태의 스레드의 상태를 Runnable상태로 만듦 > notify( ), notifyAll( )로 대체

 

 스레드와 상태제어(join) 

Thread의 join( )메소드: 스레드가 멈출 때까지 기다리게 함

  • 형태: join( ), join(long millis), join(long millis, int nanos)
  • 현재 스레드를 일시정지시킨 후 특정 스레드가 일정시간 동안 실행되도록 함
    • 시간을 명시할 경우, 현재 스레드는 TIME_WAITING 상태로 전이됨
    • 시간을 명시하지 않을 경우, 특정 스레드가 종료될 때까지 현재 스레드는 WAITING 상태로 전이됨 (특정 스레드가 종료되면 RUNNABLE 상태로 전이됨)
  • RUNNABLE 상태로 가려면 join( )메소드를 멤버로 가지는 스레드가 종료되거나, 매개값으로 주어진 시간이 지나야 함

 

join( )메소드 예시

  • MyThread5 클래스
    • sleep( )메소드로 0.5초씩 쉬면서 숫자를 출력하는 run( )메소드를 가진 MyThread5를 작성
public class MyThread5 extends Thread{
  public void run(){
    for(int i = 0; i < 5; i++){
      System.out.println("MyThread5 : "+ i);
      try {
        Thread.sleep(500);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  } *// run*
}
  • JoinExam 클래스
    • 해당 스레드를 실행하고, 해당 스레드가 종료될 때까지 기다린 후 내용을 출력하도록 join( )메소드 이용
    • join( )메소드 없이 그냥 실행하면, main 스레드 먼저 실행된 후에 MyThread5 스레드가 실행됨
    • join( )메소드는 InterruptedException이 발생하기 때문에 Exception 처리를 해줘야 함
public class JoinExam { 
  public static void main(String[] args) {
    MyThread5 thread = new MyThread5();
    *// Thread 시작*
    thread.start(); 
    System.out.println("Thread가 종료될때까지 기다립니다.");
    try {
      *// 해당 쓰레드가 멈출 때까지 멈춤*
      thread.join();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Thread가 종료되었습니다."); 
  }   
}
  • 실행결과
Thread가 종료될 때까지 기다립니다.
MyThread5 : 0
MyThread5 : 1
MyThread5 : 2
MyThread5 : 3
MyThread5 : 4
Thread가 종료되었습니다.

 

 스레드와 상태제어(wait, notify) 

wait( )메소드와 notify( )메소드: 동기화된 블록 안에서 사용해야 함

  • Object의 wait( )메소드
    • wait( )메소드를 만난 스레드가 해당 객체의 사용권(Monitoring Lock)을 가지고 있다면 해당 스레드는 Monitoring Lock을 놓고 대기해야 함(일시정지 상태가 됨) > 대기 중인 다른 메소드가 실행됨
    • 매개값으로 주어진 시간이 지나면 자동으로 실행대기 상태가 됨
    • 시간이 주어지지 않으면 notify( ), notifyAll( ) 메소드에 의해 실행대기 상태로 갈 수 있음
  • Object의 **notify( )**메소드, **notifyAll( )**메소드
    • 동기화(synchronized) 블록 내에서 wait( )메소드에 의해 일시정지 상태에 있는 스레드를 RUNNABLE 상태로 만듦

 

wait( )메소드와 notify( )메소드 예시

  • ThreadB 클래스
    • Thread를 상속받음
    • 동기화할 부분만 동기화 블록에 넣음
    • sleep( )메소드는 InterruptedException를 발생시키므로 Exception 처리를 해줘야 함
public class ThreadB extends Thread{
  *// 해당 스레드가 실행되면 자기 자신의* Monitoring Lock*을 획득*
  *// 5번 반복하면서 0.5초씩 쉬면서 total에 값을 누적*
  *// 그 후에 notify()메소드를 호출하여 wait하고 있는 스레드를 깨움*
  int total;

  @Override
  public void run(){
    synchronized(this){
      for(int i=0; i<5 ; i++){
        System.out.println(i + "를 더합니다.");
        total += i;
        try {
          Thread.sleep(500);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
      notify();
    }
  }
}
  • ThreadA 클래스
    • ThreadB를 사용하며 wait
    • wait( )메소드는 InterruptedException를 발생시키므로 Exception 처리를 해줘야 함
public class ThreadA {
  public static void main(String[] args){
    *// 앞에서 만든 쓰레드 B를 만든 후 start* 
    *// 해당 스레드가 실행되면, 해당 쓰레드는 run메소드 안에서 자신의* Monitoring Lock*을 획득*
    ThreadB b = new ThreadB();
    b.start();

    *// b에 대하여 동기화 블럭을 설정*
    *// 만약 main스레드가 아래의 블록을 위의 Thread보다 먼저 실행한다면
    // wait를 하게 되면서* Monitoring Lock*을 놓고 대기*
    synchronized(b){
      try{
        *// b.wait()메소드를 호출
        // 동기화 블록 안에서 main스레드는 정지*
        *// ThreadB가 5번 값을 더한 후 notify를 호출하게 되면 wait에서 깨어남*
        System.out.println("b가 완료될 때까지 기다립니다.");
        b.wait();
      }catch(InterruptedException e){
        e.printStackTrace();
      }

      *//깨어난 후 결과를 출력*
      System.out.println("Total is: " + b.total);
    }
  }
}
  • 실행결과
b가 완료될 때까지 기다립니다.
0를 더합니다.
1를 더합니다.
2를 더합니다.
3를 더합니다.
4를 더합니다.
Total is: 10

 

 데몬 스레드 

데몬(Daemon)

  • 보통 리눅스와 같은 유닉스계열의 운영체제에서 백그라운드로 동작하는 프로그램
  • 윈도우 같은 체제에서는 서비스라고 함

 

데몬 스레드(Daemon Thread)

  • Java에서 데몬과 유사하게 동작하는 스레드
  • 데몬 스레드는 일반 스레드(main 등)가 모두 종료되면 강제적으로 종료되는 특징을 가지고 있음
  • 데몬 스레드를 만드는 방법은 스레드에 데몬 설정을 하면 됨
  • 이런 스레드는 Java 프로그램을 만들 때 백그라운드에서 특별한 작업을 처리하게 하는 용도로 만듦
    • 작업 예시: 주기적으로 자동 저장, 일정한 시간마다 맞춤법 검사 등

 

데몬 스레드 예시

  • DaemonThread 클래스
    • Runnable 인터페이스를 구현
    • Thread 속도가 너무 빨라서 sleep( )메소드를 이용해 속도를 늦춰줌
      • sleep( )메소드: Thread클래스의 메소드로, 매개변수로 long 타입의 millis를 받으며 해당 millis(밀리초)만큼 쉬어가게 함
    • sleep( )메소드는 InterruptedException를 발생시키므로 Exception 처리를 해줘야 함
    • Exception이 발생하면 while문을 빠져나가야 하니까 break; 작성
    • Runnable 인터페이스를 구현한 DaemonThread는 Thread가 아님
      • DaemonThread 객체를 생성하기 위해서는, Thread 객체를 먼저 생성하면서 Thread 생성자 안에서 DaemonThread 객체를 생성
    • 데몬 스레드 설정하기
      • Thread 객체에 setDaemon( )메소드를 이용해 true라는 값을 주면 데몬 스레드로 설정됨
    • 데몬 스레드는 다른 스레드가 모두 종료되면 자동으로 종료됨
      • main 스레드가 먼저 종료돼버리면 데몬 스레드도 종료되니까 main 스레드의 종료시기를 늦춰줘야 함
      • main 스레드의 종료시기를 sleep( )메소드로 늦춰줌
*// Runnable을 구현하는 DaemonThread클래스를 작성*
public class DaemonThread implements Runnable {

  *// 무한루프 안에서 0.5초씩 쉬면서 '데몬 스레드가 실행중입니다.'를 출력하도록 run()메소드를 작성*
  @Override
  public void run() {
    while (true) {
      System.out.println("데몬 *스*레드가 실행중입니다.");

      try {
        Thread.sleep(500);

      } catch (InterruptedException e) {
        e.printStackTrace();
        break; *//Exception 발생 시 while 문 빠지도록*
      }
    }
  }

  public static void main(String[] args) {
    *// Runnable을 구현하는 DaemonThread를 실행하기 위하해 Thread 생성*
    Thread th = new Thread(new DaemonThread());
    *// 데몬 스레드로 설정*
    th.setDaemon(true);
    *//스쓰레드를 실행*
    th.start();

    *// main 스레드가 1초 뒤에 종료되도록 설정.*
    *// 데몬 스레드는 다른 스레드가 모두 종료되면 자동종료*
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }   
    System.out.println("메인 쓰레드가 종료됩니다. ");    
  }   
}
  • 실행결과
데몬 스레드가 실행 중입니다.
데몬 스레드가 실행 중입니다.
메인 스레드가 종료됩니다.