ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Thread 관련 - Object, ThreadGroup, ThreadLocal, volatile
    Java 2020. 1. 16. 20:18
    반응형

    Object 클래스에 선언된 Thread와 관련된 메서드들

    /* 다른 Thread가 Object 객체에 대한 notify() 메서드나 notifyAll() 메서드를
     * 호출할 때까지 현재 Thread가 대기하고 있도록 함
     * WAITING 상태로 변경됨
     */
    void wait()
    
    /* wait() 메서드와 동일한 기능을 제공
     * 매개 변수에 지정한 시간만큼만 대기
     * 매개 변수 시간을 넘어섰을 때, 현재 Thread는 다시 깨어남
     * 시간은 밀리초로 1/1,000초 단위 - 1초일 경우 1000
     */
    void wait(long timeout)
    
    /* wait() 메서드와 동일한 기능을 제공
     * 보다 자세한 밀리초(1/1,000) + 나노초(1/1,000,000,000)만큼 대기
     * 나노초는 0 ~ 999,999까지 지정 가능
     */
    void wait(long timeout, int nanos)
    
    /* 모니터
     * = lock 객체처럼 Thread Safe하게 수행하도록 도와주는 객체
     * = synchronized(monitor) { } 이런식으로 사용
     */
    
    // Object 객체의 모니터에 대기하고 있는 단일 Thread를 깨움 - 먼저 대기하는 Thread부터
    void notify()
    
    // Object 객체의 모니터에 대기하고 있는 모든 Thread를 깨움
    void notifyAll()

    사용 예

    package jamie.thread;
    
    public class StateThread extends Thread {
        private Object monitor;
        // monitor라는 이름의 객체를 매개변수로 받아 인스턴스 변수로 선언
        public StateThread(Object monitor) {
            this.monitor = monitor;
        }
        public void run() {
            try {
                // Thread를 실행중인 상태로 만들기 위해 간단하게 loop를 돌며 String 객체 생성
                for(int loop = 0; loop < 10000; loop++) {
                    String a = "A";
                }
                // synchronized 블럭 안에서 monitor 객체의 wait() 메서드를 호출
                synchronized(monitor) {
                    monitor.wait();
                }
                System.out.println(getName) + " is notified");
                // wait() 상황이 끝나면 1초간 대기했다가 해당 쓰레드 종료
                Thread.sleep(1000);
            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
        }
    }
    package jamie.thread;
    
    public class RunThreads {
        public static void main(String[] args) {
            RunTreads threads = new RunThreads();
            threads.checkThreadState();
        }
        public void checkThreadState() {
            // StateThread의 매개 변수로 넘겨줄 monitor라는 Object 클래스 작성
            Object monitor = new Object();
            StateThread thread = new StateThread(monitor);
            try {
                System.out.println("thread state = " + thread.getState());
                // Thread 객체를 생성하고 시작
                thread.start();
                System.out.println("thread state(after start) = " + thread.getState());
                
                Thread.sleep(100);
                System.out.println("thread state(after 0.1 sec) = " + thread.getState());
                
                // monitor 객체를 통해 notify() 메서드 호출
                synchronized(monitor) {
                    monitor.notify();
                }
                Thread.sleep(100);
                System.out.println("thread state(after notify) = " + thread.getState());
                
                // Thread가 종료될 때까지 기다린 후 상태 출력
                thread.join();
                System.out.println("thread state(after join) = " + thread.getState());
            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
        }
    }
    
    /* 출력 결과
     * thread state = NEW
     * thread state(after start) = RUNNABLE
     * thread state(after 0.1 sec) = WAITING
     * Thread-0 is notified.
     * thread state(after notify) = TIMED_WAITING
     * thread state(after join) = TERMINATED
     */

    ThreadGroup에서 제공하는 주요 메서드들

    ThreadGroup - Thread의 관리를 용이하게 하기 위한 Class

    기본적으로 tree 구조를 가짐 - 즉 다른 그룹에 속할 수도 있고, 다른 그룹을 포함할 수도 있음

    // 실행중인 Thread의 개수를 리턴
    int activeCount()
    
    // 실행중인 ThreadGroup의 개수를 리턴
    int activeGroupCount()
    
    /* enumerate의 리턴값 = 배열에 저장된 값의 개수
     * 위의 개수를 파악하는 메서드로 갯수 파악 후 배열을 맞춰 생성하면 됨
     */
    // 현재 ThreadGroup에 있는 모든 Thread를 매개 변수로 넘어온 Thread 배열에 담음
    int enumerate(Thread[] list)
    
    /* 현재 ThreadGroup에 있는 모든 Thread를 매개 변수로 넘어온 Thread 배열에 담음
     * 두 번째 매개 변수가 true이면 하위에 있는 ThreadGroup의 Thread 목록도 포함됨
     */
    int enumerate(Thread[] list, boolean recurse)
    
    // 현재 ThreadGroup에 있는 모든 ThreadGroup을 매개 변수로 넘어온 ThreadGroup 배열에 담음
    int enumerate(ThreadGroup[] list)
    
    /* 현재 ThreadGroup에 있는 모든 ThreadGroup을 매개 변수로 넘어온 ThreadGroup 배열에 담음
     * 두 번째 매개 변수가 true라면 하위의 ThreadGroup 목록도 포함됨
     */
    int enumerate(ThreadGroup[] list, boolean recurse)
    
    // ThreadGroup의 이름을 리턴
    String getName()
    
    // 부모 ThreadGroup을 리턴
    ThreadGroup getParent()
    
    // ThreadGroup 상세정보 출력
    void list()
    
    // 지금 ThradGroup에 속한 Thread들을 Daemon으로 지정
    void setDaemon(boolean daemon)

    ThreadLocal - 각 Thread에서 혼자 쓸 수 있는 값

    synchronized - 여러 Thread에서 데이터를 공유할 때 발생하는 문제를 해결

    ThreadLocal - Thread 별로 서로 다른 값을 처리해야 할 필요가 있을 때 사용

    ThreadLocal

    • ThreadLocal에 저장된 값은 해당 Thread에서 고유하게 사용할 수 있음

    • ThreadLocal 클래스의 변수는 private static final로 선언

    • ThreadLocal 클래스에 선언되어 있는 메서드 : set(), get(), remove(), initialValue()

    • 사용이 끝난 후에는 remove() 메서드를 호출해주는 습관을 들일 것

    remove() 메서드를 호출하는 이유
    web 기반의 application에서는 Thread를 재사용하기 위해 ThreadPool이라는 것을 사용
    ThreadPool을 사용하면, Thread가 시작된 후 그냥 끝나는 것이 아니기 때문에 remove()를 사용해서 값을 지워줘야만 해당 Thread를 다음에 사용할 때 쓰레기 값이 들어있지 않게 됨

    예제

    ThreadLocal을 사용하지 않고 OtherLogic Class에서 임의로 만든 값을 사용하려면?

    • printMyNumber() 메서드 호출시 매개 변수로 넘기기, 단) OtherLogic에서 호출하는 다른 메서드에서 해당 값을 사용하려면 또 매개 변수로 넘겨줘야 함
    • LocalUserThread에서 인스턴스 변수를 선언하여 사용, 단) 구현이 매우 복잡해질 수 있음
    package jamie.thread;
    
    import java.util.Random;
    
    public class ThreadLocalSample {
        /* local이라는 ThreadLocal 객체 생성
         * ThreadLocal은 제네릭한 클래스
         * - 객체를 생성하기 위해서는 각 Thread에서 고유하게 사용할 데이터 타입을 지정해줘야 함
         * 예제는 Integer
         */
        private final static ThreadLocal<Integer> local
          = new ThreadLocal<Integer>();
        private static Random random;
        static {
            random = new Random();
        }
        public static Integer generateNumber() {
            int value = random.nextInt(45);
            // ThreadLocal 클래스의 set() 메서드를 이용해 저장하고자 하는 값 할당
            local.set(value);
            return value;
        }
        public static Integer get() {
            // ThreadLocal 클래스의 get() 메서드를 이용하여 값을 꺼냄
            return local.get();
        }
        public static void remove() {
            // local 객체에 저장되어 있는 값을 제거하기 위해 remove() 메서드 사용
            local.remove();
        }
    }
    
    // ThreadLocal 클래스 확장 사용시, initalValue() 메서드를 Override하여 초기 값 지정도 가능함
    // 현재 Thread의 이름과 ThreadLocalSample의 get() 메서드를 사용하여 값을 읽음
    package jamie.thread;
    
    public class OtherLogic {
        public void printMyNumber() {
            System.out.println(Thread.currentThread().getName()
              + " OtherLogic value = " + ThreadLocalSample.get());
        }
    }
    package jamie.thread;
    
    public class LocalUserThread extends Thread {
        public void run() { 
            /* 간단하게 ThreadLocalSample의 generateNumber() 메서드 호출
             * 임의의 변수를 ThreadLocal에 저장하고 리턴
             */
            int value = ThreadLocalSample.generateNumber();
            System.out.println(this.getName() + " LocalUserThread value = " + value);
            OtherLogic otherLogic = new OtherLogic();
            // OtherLogic이라는 Class의 printMyNumber란 메서드 호출
            otherLogic.printMyNumber();
            // ThreadLocal에서 값을 지움
            ThreadLocalSample.remove();
        }
    }
    package jamie.thread;
    
    public class RunThreads {
        public static void main(String[] args) {
            RunTreads threads = new RunThreads();
            threads.runLocalUserThread();
        }
        public void runLocalUserThread() {
            LocalUserThread threads[] = new LocalUserThread[3];
            for (LocalUserThread thread : threads) {
                thread = new LocalUserThread();
                thread.start();
            }
        }
    }
    /* 실행 결과
     * Thread-1 LocalUserThread value=20
     * Thread-1 OhterLogic value=20
     * Thread-2 LocalUserThread value=01
     * Thread-2 OtherLogic value=01
     * Thread-3 LocalUserThread value=15
     * Thread-3 OtherLogic value=15
     *
     * LocalUserThread에서 출력한 값과 OtherLogic에서 출력한 값이 동일함
     * Thread별로 값을 절대 공유하지 않음
    */

    Java의 volatile

    변수 선언시에만 사용되는 예약어

     

    예제

    package jamie.thread;
    
    public class VolatileSample extends Thread {
        private double instanceVariable = 0;
        // 1. volatile 사용
        // private volatile double instanceVariable = 0; 
       
        void setDouble(double value) {
            this.instanceVariable = value;
        }
        public void run() {
            // instanceVariable이 0이면 계속 while문을 돌림
            while (instanceVariable == 0);
            System.out.println(instanceVariable);
        }
        
        /* 2. 대기시간을 1밀리초 줌
        public void run() {
            try {
                while (instanceVariable == 0) {
                    Thread.sleep(1)
                }
            } catch (Exception e) {}
            System.out.println(instanceVariable);
        }
        */
    }
    package jamie.thread;
    
    public class RunThreads {
        public static void main(String[] args) {
            RunTreads threads = new RunThreads();
            threads.runLocalUserThread();
        }
        public void runValatileSample() {
            VolatileSample sample = new VolatileSample();
            sample.start();
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("Sleep ended !!!");
            sample.setDouble(-1);
            System.out.println("Set value is completed !!!");
        }
    }
    
    /* 결과
     * Sleep ended !!!
     * Set value is completed !!!
     *
     * 그런데 sample Thread가 종료되지 않음
     * 원인 : CPU Cache 때문
     * Thread에서 수행되는 변수의 값을 반복적으로 참조할 때는 "메인 메모리"에 저장되지 않음
     * "CPU 캐시"에 저장되고 참조됨. 값을 바꾸게 되면, CPU 캐시의 다른 곳에 또 저장되는데,
     * 진행중인 쓰레드는 기존 값(0)을 참조함. 즉 효과 없음. 
     *
     * 1. volatile으로 선언된 Thread의 인스턴스 변수의 경우, 해당 변수 값이 바뀌면 추적을 해줌
     * 같은 객체에 있는 변수는 모든 쓰레드가 같은 값을 바라보게 됨
     * 단) 남발시 성능 저하 발생
     *
     * 모든 Thread의 인스턴스 변수에 volatile이라고 적어주지 않았다고 전부 데이터가 꼬이는건 아닌데,
     * 왜 예제는 volatile을 적지 않았을 때 문제가 생겼을까?
     * - JIT 컴파일러가 Optimization(최적화 작업)을 캐시간에 두고 최적화되었기 때문
     * 2. 즉 최적화가 되지 않아 캐시간 데이터가 다른 값을 보지 않으면 volatile을 사용할 필요가 없음
     *
     * volatile은 데이터에 문제가 있을 경우에만 사용
     */
    그 외, ThreadPool을 비롯한 Thread를 처리하는 Class는 java.util.concurrent 패키지의 Class를 참고

     

    반응형

    'Java' 카테고리의 다른 글

    Java NIO  (0) 2020.02.06
    JAVA I/O  (0) 2020.02.06
    Thread Class  (0) 2020.01.15
    Thread 개념 및 Runnable Interface VS Thread Class  (0) 2020.01.13
    java.math.BigDecimal  (0) 2020.01.13

    댓글

Designed by Tistory.