[Java] Multithreading - Using Thread

반응형

이번 글에서는 thread를 사용하는 방법에 대해 알아보도록 하겠습니다.

1. Using Thread

thread를 사용하는 방법에는 여러가지 방법들이 존재합니다.

1-1) thread priority

먼저, multi threads 들의 우선순위 할당을 통해 한정된 CPU 자원을 threads 별로 분배할 수 있습니다.

예를 들어 아래와 같이 Runnable을 구현한 구현체가 존재할 때

public class CalculatorRunnable implements Runnable {
    long value;
    CalculatorRunnable(long value) {
        this.value = value;
    }
    @Override
    public void run() {
        long startTime = System.currentTimeMillis();
        long sum = 0;
        for (int i = 0; i < value; i++) {
            sum += i;
        }
        long timeTaken = System.currentTimeMillis() - startTime;
        System.out.print("Elapsed time : " + timeTaken + " for thread: " + Thread.currentThread().getName());
    }
}

사용자는 다음과 같이 3개의 Thread에 우선순위를 부여할 수 있습니다.

 public static void main(String[] args){
     CalculatorRunnable runnable = new CalculatorRunnable(1000000000L);

     Thread t1 = new Thread(runnable);
     t1.setName("High Priority");
     t1.setPriority(Thread.MAX_PRIORITY);
     t1.start();

     Thread t3 = new Thread(runnable);
     t3.setName("Min Priority");
     t3.setPriority(Thread.MIN_PRIORITY);
     t3.start();

     Thread t2 = new Thread(runnable);
     t2.setName("Normal Priority");
     t2.setPriority(Thread.NORM_PRIORITY);
     t2.start();
 }

이때 PRIORITY가 높은 Threads에게 CPU는 자원을 더 많이 할당해 작업을 수행하게 됩니다.

그 결과 위와 같이 MAX < NORM < MIN 순서대로 작업시간이 경과한 것을 확인할 수 있습니다.

1-2) thread status & join

다음으로 status 와 join 명령어를 사용해 thread의 상태를 관리할 수 있습니다.

먼저 아래와 같은 main 함수가 있다고 가정해보겠습니다.

public static void main(String[] args){
    CalculatorRunnable runnable = new CalculatorRunnable(1000000000L);

    Thread t1 = new Thread(runnable);
    t1.setName("High Priority");
    t1.setPriority(Thread.MAX_PRIORITY);
    t1.start();

    System.out.println("System is exit");
    System.exit(0);
}

현재 위의 함수를 실행하면 t1 을 start 한뒤, t1의 종료여부와 관계 없이 main thread를 exit 하며 시스템이 종료됩니다.

이때 만약 t1이 종료된 후에만 main thread를 exit 하고자 한다면 어떻게 변경하면 좋을까요? 😅

위와 같은 경우에는 두가지 방법을 고려할 수 있습니다.

첫 번째는 'status' 를 사용하는 방법입니다. 'status' 를 사용하면 아래와 같이 변경할 수 있습니다.

public static void main(String[] args) throws InterruptedException {
    CalculatorRunnable runnable = new CalculatorRunnable(1000000000L);

    Thread t1 = new Thread(runnable);
    t1.setName("High Priority");
    t1.setPriority(Thread.MAX_PRIORITY);
    t1.start();

    // use status
    while (t1.isAlive()){
        System.out.println("t1 is alive");
        Thread.sleep(100);
    }

    System.out.println("System is exit");
    System.exit(0);
}

위의 방법을 사용하면 t1 thread를 start한 main thread가 주기적으로 t1 thread의 작업이 종료되었는지 확인하고, 종료되었을 경우에만 main thread를 exit해 프로그램을 종료합니다.

두 번째 방법은 'join' 을 사용하는 방법입니다. 'join' 함수는 isAlive 함수와 비슷하게 해당 thread의 작업 종료여부를 확인합니다. 마찬가지로 작업이 종료되었을 경우에만 다음 line으로 프로그램을 진행시킵니다.

public static void main(String[] args) throws InterruptedException {
    CalculatorRunnable runnable = new CalculatorRunnable(1000000000L);

    Thread t1 = new Thread(runnable);
    t1.setName("High Priority");
    t1.setPriority(Thread.MAX_PRIORITY);
    t1.start();

    // use join
    t1.join();

    System.out.println("System is exit");
    System.exit(0);
}

이때 join의 매개변수로는 최대 wait time을 지정할 수 있습니다.

public static void main(String[] args) throws InterruptedException {
    CalculatorRunnable runnable = new CalculatorRunnable(1000000000L);

    Thread t1 = new Thread(runnable);
    t1.setName("High Priority");
    t1.setPriority(Thread.MAX_PRIORITY);
    t1.start();

    // use join with max wait time
    t1.join(5000);

    System.out.println("System is exit");
    System.exit(0);
}

위와 같이 매개변수로 5000을 입력한 경우, 최대 5초동안 t1 thread의 작업 종료를 기다립니다. 만약 5초 이내로 t1 thread의 작업이 종료되지 않은 경우 join을 무시하고 다음 line으로 프로그램을 진행시킵니다.

1-3) daemon threads

daemon thread란 주 thread가 종료되면 함께 종료되는 thread를 의미합니다.

daemon thread는 주로 주 thread를 보조하는 역할을 수행하므로, 주 thread가 종료되면 같이 종료하도록 설계합니다.

예를 들어 아래와 같은 main 함수가 존재하는 경우

 public static void main(String[] args){
     new Thread(()->{
         while (true){
             try {
                 Thread.sleep(3000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     }).start();
     System.out.println("Expecting program shutdown now");
 }

main thread가 종료된 뒤에도, 새롭게 생성한 thread는 계속 running 중인 상태로 동작하게 됩니다.

이때 main thread 종료시, main thread에서 start한 나머지 thread들도 함께 종료시키려면 아래와 같이 해당 thread를 daemon으로 선언합니다.

 public static void main(String[] args){
     Thread daemon = new Thread(()->{
         while (true){
             try {
                 Thread.sleep(3000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     });

     // set daemon
     daemon.setDaemon(true);
     daemon.start();

     // print
     System.out.println("Expecting program shutdown now");
 }

daemon으로 선언된 thread는 주 thread가 종료될때 같이 종료되게 됩니다.

만약 daemon 으로 선언한 thread 외에도, 강제적으로 JVM 내의 모든 thread 들을 종료하고 싶을때에는 exit(0) 명령어를 사용할 수 있습니다.

 public static void main(String[] args){
     Thread daemon = new Thread(()->{
         while (true){
             try {
                 Thread.sleep(3000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     });

     // set daemon
     daemon.start();

     // print
     System.out.println("Expecting program shutdown now");

     // kill JVM threads
     System.exit(0);
 }

위와 같이 선언시, JVM 내의 모든 threads가 강제적으로 종료됩니다.

1-4) thread safely stop

thread를 안전하게 종료하는 방법은 다음과 같습니다.

기존의 stop 함수는 depreciated 되었으므로, custom thread 클래스 내부에 exit status를 사용해 직접 stop 로직을 아래와 같이 구현해야 합니다. 😎

public class CustomThread extends Thread {
    private boolean shouldExit = false;

    public void setShouldExit(boolean shouldExit) {
        this.shouldExit = shouldExit;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 30; i++) {
                if (shouldExit) {
                    break;
                }
                System.out.println("Thread is running");
                Thread.sleep(200);
            }
            System.out.println("Thread is completed");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

위의 thread는 setShouldExit 메서드를 통해 shouldExit 변수가 true로 변경되면, 진행중이던 작업을 종료하게됩니다.

추가적으로, maxWait까지만 thread의 작업 종료를 기다리고, 이를 초과한 thread를 적절히 종료시키기 위해서 아래와 같이 main 함수를 작성할 수 있습니다.

 public static void main(String[] args) throws InterruptedException {
        CustomThread stopThread = new CustomThread();
        stopThread.start();
        int maxWait = 3000;
        int wait = 0;

        while (wait < maxWait){
            Thread.sleep(300);
            wait += 300;
            if(!stopThread.isAlive()){
                System.out.println("Thread was completed within 3 seconds");
            }
        }

        // stop thread
        if(stopThread.isAlive()){
            stopThread.setShouldExit(true);
            // wait thread stopped
            stopThread.join();
        }
}

위의 main 함수는 maxWait 까지만 기다렸다가, 해당 시간이 지난 후 에도 thread가 isAlive 일 경우, setShouldExit 함수를 호출해 thread를 안전하게 종료합니다.

또한, 해당 thread가 완전히 종료될때까지 join 함수를 사용해 wait 하게됩니다.


추천서적

 

이것이 자바다:신용권의 Java 프로그래밍 정복

COUPANG

www.coupang.com

파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음


반응형

댓글

Designed by JB FACTORY