ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 쓰레드(2)
    Computer Science/JAVA 2021. 8. 20. 22:38

    1️⃣ 쓰레드의 실행제어

    효율적인 멀티쓰레드 프로그램을 만들기 위해서는 정교한 스케줄링을 통해 프로세스에게 주어진 자원과 시간을 여러 쓰레드가 낭비없이 잘 사용되도록 프로그래밍 해야함.

    [ 쓰레드 실행 과정 ]

    1. 쓰레드를 생성하고 start()를 호출하면 바로 실행되는 것이 아니라 실행 대기열에 저장되어 자신의 차례가 될 때까지 기다린다. 실행대기열은 큐의 형태로 먼저 실행 대기열에 들어온 쓰레드가 먼저 실행된다.

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

    3. 주어진 실행시간이 다 되거나 yield()를 만나면 다시 실행대기상태가 되고 다음 차례의 쓰레드가 실행상태가 된다

    4. 실행 중에 suspend(), sleep(), wait(), join(), I/O block에 의해 일시 정지 상태가 될 수 있다.

    5. 지정된 일시정지시간이 다 되거나 notify(), resume(), interrupt()가 호출되면 일시 정지상태를 벗어나 다시 실행대기열에 저장된다

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

    [ sleep() ]

    sleep()은 지정된 시간동안 쓰레드를 멈추게 한다.

    sleep()에 의해 일시정지 상태가 된 쓰레드는 지정된 시간이 다 되거나 interrupt()가 호출되면, 잠에서 깨어나 실행대기 상태가 된다. 그래서 sleep()을 호출할 때는 항상 try-catch문으로 예외처리를 해주어야 한다.

    public class ThreadEx6 {
        public static void main(String[] args) {
            Thread th1 = new ThreadEx6_1();
            Thread th2 = new ThreadEx6_2();
    
            th1.start();
            th2.start();
    
            try {
                th1.sleep(2000);
            } catch (InterruptedException ignored) {}
    
            System.out.print("<main종료>");
        }
    }
    
    class ThreadEx6_1 extends Thread {
    
        @Override
        public void run() {
            for(int i = 0; i < 300; i++)
                System.out.print("-");
            System.out.print("<th1 종료>");
        }
    }
    
    class ThreadEx6_2 extends Thread {
    
        @Override
        public void run() {
            for(int i = 0; i < 300; i++)
                System.out.print("|");
            System.out.print("<th2 종료>");
        }
    }

    위 코드에서 th1.sleep(2000)을 했기 때문에 th1 쓰레드가 가장 늦게 끝나야 될 것 같지만 가장 빨리 종료되었다.

    그 이유는 sleep()이 항상 현재 실행 중인 쓰레드에 대해 작동하기 때문에 th1.sleep()이라고 호출하였지만 실제로 영향 받는 것은 main메서드를 실행하는 main쓰레드이기 때문이다.

    [ interrupt(), interrupted() ]

    interrupt()는 쓰레드에게 작업을 멈추라고 요청한다. interrupted()는 쓰레드에 대해 interrupt()가 호출되었는지 알려준다.

    쓰레드가 sleep(), wait(), join()에 의해 일시정지 상태에 있을 때, 해당 쓰레드에 대해 Interrupt()를 호출하면, sleep(), wait(), join()에서 Interrupted Exception이 발생하고 쓰레드는 실행 대기 상태로 바뀐다

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

    [ suspend(), resume(), stop() ]

    suspend()는 sleep()처럼 쓰레드를 멈추게 한다. suspend()에 의해 정지된 쓰레드는 resume()을 호출해야 다시 실행대기 상태가 된다. stop()은 호출되는 즉시 쓰레드가 종료된다.

    현재는 deprecated되었기 때문에 사용이 권장되지는 않는다.

    [ yield() ]

    yield()는 쓰레드 자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보합니다. 예를 들어 스케줄러에 의해 1초의 실행시간을 할당받은 쓰레드가 0.5초의 시간동안 작업한 상태에서 yield()가 호출되면, 나머지 0.5초는 포기하고 다시 실행대기상태가 됩니다.

    yield와 interrupt를 적절히 사용하면, 프로그램의 응답성을 높이고 보다 효율적인 실행이 가능하게 할 수 있습니다.

    public class ThreadEx7 {
        public static void main(String[] args) {
            ThreadEx7_1 th1 = new ThreadEx7_1("*");
            ThreadEx7_1 th2 = new ThreadEx7_1("**");
            ThreadEx7_1 th3 = new ThreadEx7_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 ignored) {}
        }
    }
    
    class ThreadEx7_1 implements Runnable {
        boolean suspended = false;
        boolean stopped = false;
    
        Thread th;
    
        ThreadEx7_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(); // 현재 스레드가 suspend라면 다른 스레드에게 양보
                }
            }
            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();
        }
    }

    현재 실행 중인 쓰레드가 suspend 상태라면 yield()를 호출해서 다음 쓰레드에 양보하고 있습니다.

    stop()이 호출되었을 때, 쓰레드가 일시정지 상태에 머물러 있는 상황이라면 stopped 값이 바뀌었어도 쓰레드가 정지될 때까지 최대 1초는 기다려야 하지만, interrupt()를 호출하면 sleep()에서 InterruptedException이 발생하여 즉시 일시정지 상태를 벗어나게 됩니다.

    [ join() ]

    쓰레드는 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 할 때 join()을 사용합니다. 시간을 지정하지 않으면, 해당 쓰레드가 작업을 모두 마칠 때까지 기다리게 됩니다. 작업 중에 다른 쓰레드의 작업이 먼저 수행되어야할 필요가 있을 때 join()을 사용합니다.

    join()도 sleep()처럼 interrupt()에 의해 대기 상태에서 벗어날 수 있으며, join()이 호출되는 부분을 try-catch로 감싸야합니다.

    join()을 이용한 유사 Garbage Collector

    public class ThreadEx8 {
        public static void main(String[] args) {
            ThreadEx8_1 gc = new ThreadEx8_1();
            gc.setDaemon(true);
            gc.start();
    
            int requiredMemory = 0;
    
            for(int i = 0; i < 20; i++) {
                requiredMemory = (int)(Math.random() * 10) * 20;
    
                if(gc.freeMemory() < requiredMemory
                    || gc.freeMemory() < gc.totalMemory() * 0.4) {
                    gc.interrupt(); // 일어나서 일해라
                    try {
                        gc.join(100);
                    } catch (InterruptedException ignored) {}
                }
                gc.usedMemory += requiredMemory;
                System.out.println("usedMemory: " + gc.usedMemory);
            }
        }
    }
    
    class ThreadEx8_1 extends Thread {
        final static int MAX_MEMORY = 1000;
        int usedMemory = 0;
    
        @Override
        public void run() {
            while(true) {
                try {
                    Thread.sleep(10 * 1000);
                } catch (InterruptedException e) {
                    System.out.println("Awaken by interrupt().");
                }
    
                gc();
                System.out.println("Garbage Collected. Free Momory :" + freeMemory());
            }
        }
    
        public void gc() {
            usedMemory -= 300;
            if(usedMemory < 0)
                usedMemory = 0;
        }
    
        public int totalMemory() {
            return MAX_MEMORY;
        }
    
        public int freeMemory() {
            return MAX_MEMORY - usedMemory;
        }
    }
    • 가비지 컬렉터를 데몬 쓰레드로 호출

    • 조건에 부합하면 interrupt()되어서 실행된다.

    • interrupt()가 실행되어 gc를 수행하지만, 그 사이 main 쓰레드가 실행되지 않도록, join()을 실행시킨다.

    Uploaded by Notion2Tistory v1.1.0

    'Computer Science > JAVA' 카테고리의 다른 글

    쓰레드(1)  (0) 2021.08.20
    애너테이션  (0) 2021.08.20
    열거형(enums)  (0) 2021.08.20
    Generics  (0) 2021.08.20
    String, StringBuilder, StringBuffer  (0) 2021.08.20

    댓글

Designed by Tistory.