ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] 멀티 스레드 - 상태 제어
    CSE/Java 2015. 12. 12. 18:55

    멀티 스레드는 여러 절로 구성되어 있습니다.

     


    Intro

    작업스레드

    스레드 우선순위 & 동기화 메소드와 동기화 블록



     


    상태 제어
     스레드 간 협업(wait(), notify(), notifyAll())
      경우에 따라서는 두 개의 스레드를 교대로 번갈아가며 실행해야 할 경우가 있습니다. 정확한 교대작업이 필요할 경우, 자신의 작업이 끝나면 상대방 스레드를 일시 정지 상태에서 풀어주고, 자신은 일시 정지 상태로 만드는 것입니다. 이 방법의 핵심은 공유 객체에 있습니다.

      공유 객체는 두 스레드가 작업할 내용을 각각 동기화 메소드로 구분해 놓습니다. 한 스레드가 작업을 완료하면 notify() 메소드로 풀어주고, 자신은 wait() 메소드를 호출하여 일시 정지 상태로 만듭니다.

     






      wait() 메소드의 시간 파라미터를 주게 되면 notify()를 호출하지 않아도 지정 시간이 지나면 스레드가 자동적으로 실행 대기 상태가 됩니다. 

      notify() 메소드와 동일한 역할을 하는 notifyAll() 메소드도 있는데, notify() 는 wait()에 의해 일시 정지된 스레드 중 한개를 실행 대기 상태로 만들고, notifyAll() 메소드는 wait()에 의해 일시 정지된 모든 스레드들을 실행 대기 상태로 만듭니다.

      위 메소드들은 동기화 메소드 또는 동기화 블록 내에서만 사용할 수 있습니다.

      다음 예제는 두 스레드의 작업을 WorkObject의 methodA()와 methodB()에 정의해 두고, 두 스레드 ThreadA와 ThreadB가 교대로 methodA()와 methodB()를 호출하게 했습니다.



     * WorkObject.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package mT;
     
    public class WorkObject {
        public synchronized void methodA() {
            System.out.println("ThreadA의 methodA() 작업 실행");
            notify(); // 일시 정지 상태에 있는 ThreadB를 실행 대기 상태로 만듬
     
            try {
                wait(); // ThreadA를 일시 정지 상태로 만듬
            } catch (InterruptedException e) {
            }
        }
     
        public synchronized void methodB() {
            System.out.println("ThreadB의 methodB() 작업 실행");
            notify();
     
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
     
    cs


     * ThreadA.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
    package mT;
     
    public class ThreadA extends Thread {
        
        private WorkObject workObject;
        
        public ThreadA(WorkObject workObject) {
            this.workObject = workObject;
        }
        
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                workObject.methodA();
            }
        }
     
    }
     
    cs


     * ThreadB.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
    package mT;
     
    public class ThreadB extends Thread {
        
        private WorkObject workObject;
        
        public ThreadB(WorkObject workObject) {
            this.workObject = workObject;
        }
        
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                workObject.methodB();
            }
        }
     
    }
     
    cs





     * WaitNotifyExam.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    package mT;
     
    public class WaitNotifyExam {
        public static void main(String[] args) {
            WorkObject workObject = new WorkObject();
     
            ThreadA threadA = new ThreadA(workObject);
            ThreadB threadB = new ThreadB(workObject);
     
            threadA.start();
            threadB.start();
        }
    }
     
    cs



     두 프로세스가 한치에 오차없이 번갈아 출력한다는 것을 확인할 수 있습니다.





      다음 예제는 데이터를 저장하는 스레드가 데이터를 저장하면, 데이터를 소비하는 스레드가 데이터를 읽고 처리하는 과정을 예제로 보겠습니다.

      생성자 스레드는 소비자 스레드가 읽기 전에 새로운 데이터를 두 번 생성하면 안 되고(setData() 메소드를 두 번 실행하면 안 됨), 소비자 스레드는 생성자 스레드가 데이터를 생성하기 전에 이전 데이터를 두 번 읽어서는 안 됩니다.




     * DataBox.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
     
    package mT;
     
    public class DataBox {
        private String data;
     
        public synchronized String getData() {
            if (this.data == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
     
            String returnValue = data;
            System.out.println("ConsumerThread가 읽은 데이터: " + returnValue);
            data = null;
            ;
            notify();
     
            return returnValue;
     
        }
     
        public synchronized void setData(String data) {
            if (this.data != null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
     
            this.data = data;
            System.out.println("ProducerThread가 생성한 데이터: " + data);
            notify();
        }
     
    }
     
    cs


     * ProducerThread.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     
    package mT;
     
    public class ProducerThread extends Thread {
     
        private DataBox dataBox;
     
        public ProducerThread(DataBox dataBox) {
            this.dataBox = dataBox;
        }
     
        @Override
        public void run() {
            for (int i = 1; i <= 3; i++) {
                String data = "Data-" + i;
                dataBox.setData(data);
            }
        }
     
    }
     
    cs


     * ConsumerThread.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    package mT;
     
    public class ConsumerThread extends Thread {
     
        private DataBox dataBox;
     
        public ConsumerThread(DataBox dataBox) {
            this.dataBox = dataBox;
        }
     
        @Override
        public void run() {
            for (int i = 1; i <= 3; i++) {
                String data = dataBox.getData();
            }
        }
    }
     
    cs


     * WaitNotifyExam2.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    package mT;
     
    public class WaitNotifyExam2 {
        public static void main(String[] args) {
            DataBox dataBox = new DataBox();
     
            ProducerThread pThread = new ProducerThread(dataBox);
            ConsumerThread cThread = new ConsumerThread(dataBox);
     
            pThread.start();
            cThread.start();
        }
    }
     
    cs











     스레드의 안전한 종료(stop 플래그, interrupt())
      스레드는 자신의 run() 메소드가 모두 실행되면 자동적으로 종료합니다. 하지만 경우에 따라 실행 중인 스레드를 즉시 종료시킬 필요가 있습니다.

      * 기존의 stop() 메소드가 있었지만 Deprecated 되었습니다. stop() 메소드를 통해서 자원의 safety가 지켜지지 않기 때문입니다.

      

      stop 플래그를 이용하는 방법
       스레드는 run() 메소드가 끝나면 자동적으로 종료되므로, run() 메소드가 정상적으로 종료되도록 유도하는 것이 최선의 방법입니다.

       다음 코드는 stop 플래그를 이용해서 run() 메소드의 종료를 유도합니다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class XXXThread extends Thread {
        private boolean stop;    // stop flag
     
        public void run() {
            while (!stop) {
     
            }
            
        }
    }
     
    cs
      



       위 코드에서 stop 필드가 false 일 경우에는 while문의 조건식이 true가 되어 반복 실행되지만, stop 필드가 true 일 경우 while 문의 조건식이 false가 되어 while문을 빠져나옵니다. 

      
       다음 예제는 PrintThread1을 실행한 후 1초 후에 PrintThread1을 멈추도록 setStop() 메소드를 호출합니다.



     * StopFlagExam.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
    package mT;
     
    public class StopFlagExam {
        public static void main(String[] args) {
            PrintThread1 pThread = new PrintThread1();
            pThread.start();
     
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
     
            }
            
            pThread.setStop(true);    // 스레드 종료를 위한 stop 플래그 조작
        }
    }
     
    cs


     * PrintThread1.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    package mT;
     
    public class PrintThread1 extends Thread {
        
        private boolean stop;        // stop flag
        
        public void setStop(boolean stop) {
            this.stop = stop;
        }
        
        public void run() {
            while(!stop) {
                System.out.println("실행 중");
            }
            
            System.out.println("자원 정리");
            System.out.println("종료");
        }
     
    }
     
    cs









      interrupt() 메소드를 이용하는 방법
       interrupt() 메소드는 스레드가 일시 정지 상태에 있을 때 InterruptedException 예외를 발생시키는 역할을 합니다. 이 메소드를 이용하면 run() 메소드를 정상 종료 할 수 있습니다.

       예를 들어 다음 그림과 같이 ThreadA와 ThreadB를 생성해서 start() 메소드로 ThreadB를 실행시켰다고 가정해봅시다.












       ThreadA가 ThreadB의 interrupt() 메소드를 실행하게 되면 ThreadB가 sleep() 메소드로 일시 정지 상태가 될 때 ThreadB에서 InterruptB에서 InterruptedException이 발생하여 예외 처리(catch) 블록으로 이동합니다. 결국 ThreadB는 while문을 빠져나와 run() 메소드를 정상 종료하게 됩니다. 

       다음 예제는 PrintThread2를 실행한 후 1초 후에 PrintThread2를 멈추도록 interrupt() 메소드를 호출합니다.



     * InterruptExam.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package mT;
     
    public class InterruptExam {
        public static void main(String[] args) {
            Thread thread = new PrintThread2();
            
            thread.start();
            
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            
            thread.interrupt();
        }
    }
     
    cs


     * PrintThread2.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
    package mT;
     
    public class PrintThread2 extends Thread {
     
        public void run() {
            try {
                while (true) {
                    System.out.println("실행 중");
                    Thread.sleep(1);
                }
            } catch (InterruptedException e) {
            }
     
            System.out.println("자원 정리");
            System.out.println("종료");
        }
     
    }
     
    cs








       주목할 점은 스레드가 실행 대기 또는 실행 상태에 있을 때 interrupt() 메소드가 실행되면 즉시 InterruptedException 예외가 발생하지 않고, 스레드가 미래에 일시 정지 상태가 되면 InterruptedException 예외가 발생한다는 것입니다. 따라서 스레드가 일시 정지 상태가 되지 않으면 interrupt() 메소드 호출은 아무 의미가 없습니다.

       일시 정지를 만들지 않고도 interrupt() 호출 여부를 알 수 있는 방법이 있습니다. interrupt() 메소드가 호출되었다면 스레드의 interrupted() 와 isInterrupted() 메소드는 true을 리턴합니다. interrupted()는 정적 메소드로 현재 스레드가 interrupted 되었는지 확인하고, isInterrupted()는 인스턴스 메소드로 현재 스레드가 interrupted 되었는지 확인할 때 사용합니다.


    1
    2
    boolean status = Thread.interrupted();
    boolean status2 = objThread.isInterrupted();
    cs




       












     * 이 포스트은 서적 '이것이 자바다' 를 참고하여 작성한 포스트입니다.



    댓글

Designed by Tistory.