1. 데몬 쓰레드

데몬 쓰레드는 다른 일반 쓰레드의 작업을 돕는 보조적인 역할을 수행하는 쓰레드이다. 일반 쓰레드가 모두 종료되면 데몬 쓰레드는 강제적으로 자동 종료되는데, 그 이유는 데몬 쓰레드는 일반 쓰레드의 보조적인 역할을 수행하므로 일반 쓰레드가 모두 종료되고나면 데몬 쓰레드의 존재의 의미가 없기 때문이다. 이 점을 제외하고는 데몬 쓰레드와 일반 쓰레드는 다르지 않다. 데몬 쓰레드의 예로는 가비지 컬렉터, 워드프로세서의 자동저장, 화면자동갱신 등이 있다.

 

데몬 쓰레드는 무한 루프와 조건문을 이용해서 실행 후 대기하고 있다가 특정 조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다.

 

package thread;

public class ThreadEx10 implements Runnable {

    static boolean autoSave = false;

    public static void main(String[] args) {
        Thread t = new Thread(new ThreadEx10());
        t.setDaemon(true);
        t.start();

        for(int i=0; i <= 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {

            }
            System.out.println(i);

            if(i == 5)
                autoSave = true;
        }
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(3*1000); // 3초마다 실행됨
            } catch(InterruptedException e) {

            }

            if(autoSave) {
                autoSave();
            }

        }
    }

    public void autoSave() {
        System.out.println("작업 파일이 자동 저장되었습니다.");
    }
}

[실행 결과]

2. 쓰레드의 실행 제어

쓰레드 프로그래밍이 어려운 이유는 동기화와 스케줄링 때문이다. 쓰레드의 스케줄링을 잘하기 위해서는 쓰레드의 상태와 관련 메서드를 잘 알아야 하는데, 먼저 쓰레드의 스케줄링과 관련된 메서드는 다음과 같다.

메서드 설 명
static void sleep(long millis)
static void slepp(long mills, int nanos)
지정된 시간(천분의 일초 단위)동안 쓰레드를 일시정지시킨다. 지정된 시간이 지나고나면 자동적으로 다시 실행 대기 상태가 된다.
void join()
void join(long millis)
void join(long millis, int nanos)
지정된 시간동안 쓰레드가 실행되도록한다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속한다.
void interrupt() sleep()이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만든다. 해당 쓰레드에서는 InterruptedException이 발생함으로써 일시정지상태를 벗어나게 된다.
void stop() 쓰레드를 즉시 종료시킨다.
void suspend() 쓰레드를 일시정지시킨다. resume()을 호출하면 다시 실행대기상태로 만든다.
void resume() suspend()에 의해서 일시정지상태에 있는 쓰레드를 실행대기상태로 만든다.
static void yield() 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보(yield)하고 자신은 실행대기상태가 된다.

 

쓰레드의 상태는 다음과 같다.

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

1. 쓰레드를 생성하고 start() 메서드를 호출하면 바로 실행되는 것이 아니라 실행디기열에 저장되어 자기 차례를 기다린다.

2. 실행 대기 상태에 있다가 자신의 차례가 되면 실행 상태가 된다.

3. 주어진 실행시간이 다 되거나 yield()를 만나면 다시 실행 대기 상태가 된다.

4. 실행중에 suspend(), sleep(), wait(), join(), I/O Block에 의해 일시정지상태가 될 수 있다. I/O Block은 입출력 작업에서 발생하는 지연 상태를 말한다. 사용자의 입력을 기다리는 경우를 예로 들 수 있는데, 이런 경우 일시정지 상태에 있다가 사용자가 입력을 마치면 다시 실행대기 상태가 된다.

5. 지정된 일시정지시간이 다되거나, notifiy(), resume(), interrupt()가 호출되면 일시 정지 상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 된다.

6. 실행을 모두 마치거나 stop()이 호출되면 쓰레드는 소멸된다.

 

9.1 sleep(long millis) - 일정시간동안 쓰레드 중지

sleep에 의해 일시 정지 상태가 된 쓰레드는 지정된 시간이 다 되거나 interrupt()가 호출되면 (InterruptedException 발생), 잠에서 깨어나 실행대기 상태가 된다. 그래서 sleep()을 호출할 때는 항상 try-catch문을 예외를 처리해줘야한다. 매번 try - catch를 하는게 번거롭기 때문에 아래와 같은 메소드를 만들어서 사용하기도 한다.

void delay(long millis) {

    try {
        Thread.sleep(millis);
    } catch(InterruptedException e) {
    	
    }

}

 

9.2 interrupt와 interrupted() - 쓰레드의 작업을 취소한다.

진행 중인 쓰레드의 작업이 끝나기 전에 취소시켜야할 때가 있다. 예를 들어 큰 파일을 다운로드 받을 때 시간이 너무 오래 걸리면 중간에 다운로들르 포기하고 취소할 수 있어야한다. interrupt()는 쓰레드에게 작업을 멈추라고 요청한다. 단지 멈추라고 요청만 하는 것일 뿐 쓰레드를 강제로 종료시키지는 못한다. interrupt()는 그저 쓰레드의 interrupted상태(인스턴스 변수)를 바꾸는 것일 뿐이다.

 

그리고 interrupted()는 쓰레드에 대해 interrupt()가 호출되었는지 알려준다. interupt()가 호출되지 않았다면 false를, interrupt()가 호출되었다면 true를 반환한다.

Thread th = new Thread();
th.start();
...

th.interrupt(); // 쓰레드 th에 interrupt()를 호출한다.

class MyThread extends Thread {


    public void run() {
         while(!interrupted()) { // interrupted()의 결과가 false인 동안 반복
             ...
         }
    }

}
  • void interrupt() : 쓰레드의 interrupted 상태를 false에서 true로 변경
  • boolean isInterrupted() : 쓰레드의 interrupted 상태를 반환
  • static boolean interrupted() : 현재 쓰레드의 interrupted 상태를 반환 후, false로 변경

쓰레드가 sleep(), wait(), join()에 의해 일시정지 상태에 있을 때 해당 쓰레드에 대해 interrupt()를 호출하면 InterruptedException이 발생하여 실행 대기 상태로 바뀐다. 즉 멈춰있던 쓰레드를 꺠워서 실행 가능한 상태로 만드는 것이다.

 

다음 예제는 입력값을 입력하면 카운트가 종료되는 예제이다.

package thread;

import javax.swing.*;

public class ThreadEx13 {

    public static void main(String[] args) {
        ThreadEx13_1 th1 = new ThreadEx13_1();
        th1.start();
        String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
        System.out.println("입력하신 값은 " + input + "입니다. ");
        th1.interrupt(); // interrupt()를 호출하면, interrupted 상태가 true가 됩니다.
        System.out.println("isInterrupted(): " + th1.isInterrupted()); // true
    }

    static class ThreadEx13_1 extends Thread {
        @Override
        public void run() {
            int i = 10;
            while (1 !=0 && !isInterrupted()) {
                System.out.println(i--);
                for(long x=0;x<25000000L;x++); // 시간 지연
            }
            System.out.println("카운트가 종료되었습니다.");
        }
    }

}

[실행 결과]

-1138
-1139
-1140
-1141
-1142
-1143
-1144
-1145
-1146
-1147
-1148
입력하신 값은 abcd입니다. 
isInterrupted(): true
카운트가 종료되었습니다.

 

for문 시간 지연 대신에 Thread.sleep을 넣고 입력값을 입력했을 때를 보면 멈추지 않는 것을 볼 수 있다. Thread.sleep(1000)에서 InterruptedException이 발생했기 때문이다. sleep()에 의해 쓰레드가 잠시 멈춰있을 때, interrupt()를 호출하면 InterruptedException이 발생되고 쓰레드의 interrupted 상태는 false로 자동 초기화된다. 그럴 때는 catch 블록에 interrupt()를 추가로 넣어줘서 쓰레드의 interrupted 상태를 true로 다시 바꿔줘야한다.

package thread;

import javax.swing.*;

public class ThreadEx14 {

    public static void main(String[] args) {
        ThreadEx14_1 th1 = new ThreadEx14_1();
        th1.start();
        String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
        System.out.println("입력하신 값은 " + input + "입니다. ");
        th1.interrupt(); // interrupt()를 호출하면, interrupted 상태가 true가 됩니다.
        System.out.println("isInterrupted(): " + th1.isInterrupted()); // true
    }

    static class ThreadEx14_1 extends Thread {
        @Override
        public void run() {
            int i = 10;
            while (1 !=0 && !isInterrupted()) {
                System.out.println(i--);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e){

                }
            }
            System.out.println("카운트가 종료되었습니다.");
        }
    }

}

 

9.3 suspend(), resume(), stop()

suspend()는 sleep()처럼 쓰레드를 멈추게 한다. suspend()에 의해 정지된 쓰레드는 resume()을 호출해야 다시 실행대기 상태가 된다. stop()은 쓰레드의 호출되는 즉시 쓰레드가 종료된다. suspend(), resume(), stop()은 쓰레드의 실행을 제어하는 가장 쉬운 방법이지만 suspend()와 stop()이 교착상태를 일으키기 쉽게 작성되어 있으므로 사용이 권장되지 않는다. 그래서 이 메소드들은 deprecated 되었다.

 

9.4 yield() 다른 쓰레드에게 양보한다.

yield()는 쓰레드 자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보한다. yield()와 interrupted()를 적절히 사용하면, 프로그램의 응답성을 높이고 보다 효율적인 실행이 가능하게 할 수 있다. suspended가 true일 경우에는 yield를 호출하여 남은 실행 시간을 무의미한 while문에 낭비하지 않고 다른 쓰레드에게 양보하게 되므로 더 효율적이다.

package thread;

public class ThreadEx18 {

    public static void main(String[] args) {

        ThreadEx18_1 th1 = new ThreadEx18_1("*");
        ThreadEx18_1 th2 = new ThreadEx18_1("**");
        ThreadEx18_1 th3 = new ThreadEx18_1("***");

        th1.start();
        th2.start();
        th3.start();

        try {
            Thread.sleep(2000);
            th1.suspend();
            Thread.sleep(2000);
            th2.suspend();
            Thread.sleep(3000);
            th1.resume();
            Thread.sleep(3000);
            th1.stop();
            th2.stop();
            Thread.sleep(2000);
            th3.stop();
        } catch (InterruptedException e) {}

    }

    static class ThreadEx18_1 implements Runnable {
        boolean suspended = false;
        boolean stopped = false;

        Thread th;

        ThreadEx18_1(String name) {
            th = new Thread(this, name);
        }

        @Override
        public void run() {
            String name = th.getName();
            while (!stopped) {
                if(!suspended) {
                    System.out.println(name);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        System.out.println(name + " - interrupted");
                    }
                } else {
                    Thread.yield();
                }
            }
            System.out.println(name + " - stopped");
        }

        public void suspend() {
            suspended = true;
            th.interrupt();
            System.out.println(th.getName() + " - interrupt() by suspend()");
        }

        public void stop() {
            stopped = true;
            th.interrupt();
            System.out.println(th.getName() + " - interrupt() by stop()");
        }

        public void resume() { suspended = false; }
        public void start() { th.start(); }
    }

}

 

9.5 join() - 다른 쓰레드의 작업을 기다린다.

쓰레드 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 할 때 join을 사용한다.

  • void join() 
  • void join(long millis)
  • void join(long millis, int nanos)

시간을 지정하지 않으면 해당 쓰레드가 작업을 모두 마칠때까지 기다리게 된다. 작업 중에 다른 쓰레드의 작업이 먼저 수행되어야할 필요가 있을 때 join()을 사용한다. 아래 코드를 실행했을 때 join을 사용해서 th1과 th2 작업을 모두 마칠 때 까지 main 쓰레드가 기다리도록 한다. 그리고 2개의 쓰레드가 모두 끝나면 작업에 소요된 시간을 출력한다.

package thread;

public class ThreadEx19 {

    static long startTime = 0;

    public static void main(String[] args) {
        ThreadEx19_1 th1 = new ThreadEx19_1();
        ThreadEx19_2 th2 = new ThreadEx19_2();
        th1.start();
        th2.start();
        startTime = System.currentTimeMillis();

        try {
            th1.join();
            th2.join();
        } catch (InterruptedException e) {

        }
        System.out.println("소요시간: " + (System.currentTimeMillis() - ThreadEx19.startTime));
    }

    static class ThreadEx19_1 extends Thread {
        @Override
        public void run() {
            for(int i=0; i < 300; i++) {
                System.out.println(new String("-"));
            }
        }
    }

    static class ThreadEx19_2 extends Thread {
        @Override
        public void run() {
            for(int i=0; i < 300; i++) {
                System.out.println(new String("|"));
            }
        }
    }
}

 

 

Reference

http://www.yes24.com/Product/Goods/24259565

 

Java의 정석 - YES24

최근 7년동안 자바 분야의 베스트 셀러 1위를 지켜온 `자바의 정석`의 최신판. 저자가 카페에서 12년간 직접 독자들에게 답변을 해오면서 초보자가 어려워하는 부분을 잘 파악하고 쓴 책. 뿐만 아

www.yes24.com

 

'Java' 카테고리의 다른 글

[Java] Thread(쓰레드) - 5  (0) 2022.06.02
[Java] Thread(쓰레드) - 3  (0) 2022.05.31
[Java] Thread(쓰레드) - 2  (0) 2022.05.30
[Java] Thread(쓰레드) - 1  (0) 2022.05.30
[Java] 코딩 컨밴션 (Code Conventions )  (0) 2021.04.11
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기