Programing/Java

Effective Java - 스레드(1)

안중환 2016. 3. 7. 13:01
  • Thread 설계
    - Step 1. Single Thread Model
     AutoCloseable Interface

     
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.concurrent.Executor;
    import java.util.concurrent.Executors;
    
    // C99, C11(thread) - Android M
    // C++11/14
    // Java 8, 9
    
    public class Example3 {
        /*
       private static void handleRequest(Socket connection) {
          OutputStream os = null;
          try {
              os = connection.getOutputStream();
              os.write("Hello World".getBytes());
              os.flush();
          } catch (IOException e) {
              e.printStackTrace();
          } finally {
              if (os != null)
                  try {
                      os.close();
                  } catch (IOException e) {
    
                  }
          }
      }
      */
    
        // Try with Resource : Java 7 - (C# using block)
        // 1. Connection, Socket, Stream 비 메모리 자원을 해지하는
        //   명시적인 종료 메소드를 호출해야 하는 것은 코드를 어지럽힌다.
        // 2. try 블록에 존재하는 자원을 자동으로 회수해준다.
        // 3. AutoCloseable Interface
        private static void handleRequest(Socket connection) {
            try (OutputStream os = connection.getOutputStream();
                 InputStream is = connection.getInputStream()) {
                os.write("Hello World".getBytes());
                os.flush();
            } catch (IOException e) {
            }
        }
    
        
        // Step 1. Single Thread Model
        // 1) 한번에 하나의 요청을 처리하는 것이 가능하다.
        // 2) 단일 스레드에서 IO 작업 등으로 대기하는 경우, 하드웨어 자원을
        //    효과적으로 사용할 수 없다.
        public static void main(String[] args) throws IOException {
            ServerSocket socket = new ServerSocket(8080);
    
            while (true) {
                Socket connection = socket.accept();
                handleRequest(connection);
            }
        }
    

    - Step 2. Thread per Connection Model
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.concurrent.Executor;
    import java.util.concurrent.Executors;
    
    // C99, C11(thread) - Android M
    // C++11/14
    // Java 8, 9
    
    public class Example3 {
        // Try with Resource : Java 7 - (C# using block)
        // 1. Connection, Socket, Stream 비 메모리 자원을 해지하는
        //   명시적인 종료 메소드를 호출해야 하는 것은 코드를 어지럽힌다.
        // 2. try 블록에 존재하는 자원을 자동으로 회수해준다.
        // 3. AutoCloseable Interface
        private static void handleRequest(Socket connection) {
            try (OutputStream os = connection.getOutputStream();
                 InputStream is = connection.getInputStream()) {
                os.write("Hello World".getBytes());
                os.flush();
            } catch (IOException e) {
            }
        }
        
    
        
        // Step 2. Thread per Connection Model
        // 순차적인 실행 방법보다 훨씬 더 많은 작업을 수행하는 것이 가능하다.
    
        // 문제점
        // 1. 엄청나게 많은 스레드가 생성된다.
        // 2. Thread 를 만들고 제거하는 작업에도 자원이 소모된다.
        // : 클라이언트의 요청이 작은 단위로 일어나는 경우에는 더욱더 문제가 심하다.
        // 3. 자원 낭비가 심하다.
        //  1) 실행 중인 스레드는 메모리를 많이 소모한다.
        //     Java의 스택은 두 개이다.(Java, Native)
        //  2) 프로세서의 개수 보다 많은 스레드가 생성되면 대부분의 스레드는
        //     대기 상태에 있다.
        // 4. 컨텍스트 스위칭의 비용이 크다.
        // 5. OS 에서 생성할 수 있는 스레드의 개수는 제한되어 있다.
        public static void main(String[] args) throws IOException {
            ServerSocket socket = new ServerSocket(8080);
            while (true) {
                final Socket connection = socket.accept();
                Runnable task = new Runnable() {
                    @Override
                    public void run() {
                        handleRequest(connection);
                    }
                };
    
                new Thread(task).start();
            }
        }
    

    - Step 3. Pool-based Model
     
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.concurrent.Executor;
    import java.util.concurrent.Executors;
    
    // C99, C11(thread) - Android M
    // C++11/14
    // Java 8, 9
    
    public class Example3 {
        // Try with Resource : Java 7 - (C# using block)
        // 1. Connection, Socket, Stream 비 메모리 자원을 해지하는
        //   명시적인 종료 메소드를 호출해야 하는 것은 코드를 어지럽힌다.
        // 2. try 블록에 존재하는 자원을 자동으로 회수해준다.
        // 3. AutoCloseable Interface
        private static void handleRequest(Socket connection) {
            try (OutputStream os = connection.getOutputStream();
                 InputStream is = connection.getInputStream()) {
                os.write("Hello World".getBytes());
                os.flush();
            } catch (IOException e) {
            }
        }
        
        // Step 3. Pool-based Model
        // 순차적으로 문제를 해결하는 방법은 응답 속도와 전체적인 성능이 떨어지는
        // 문제점이 있고 작업별로 스레드를 만들어 내는 방법은 자원 관리 측면에서
        // 문제점이 있다
    
        // 해결책)
        // 1) 스레드의 생성을 제한해야 한다 -> Thread pool
        // 2) 생산자 & 소비자 모델
    
        public static final int NTHREADS = 100;
        public static final Executor exec = Executors.newFixedThreadPool(NTHREADS);
        // 1) newFixedThreadPool
        // 작업이 등록되면 제한된 개수까지 스레드를 생성해서 해당 작업을 처리한다.
        // 생성 이후에 더 이상 생성하지 않고 스레드 수를 유지한다.
    
        // 2) newCachedThreadPool
        // 스레드 수가 처리할 작업보다 많아서 쉬는 스레드가 발생하면,
        // 스레드를 종료한다.
    
        // 3) newSingleThreadPool
        // 단일 스레드로 처리한다. 등록된 작업은 다양한 정책에 의해서 수행될 수 있다.
        // (LIFO, FIFO, 우선순위)
        public static void main(String[] args) throws IOException {
            ServerSocket socket = new ServerSocket(8080);
    
            while (true) {
                Socket connection = socket.accept();
                Runnable task = new Runnable() {
                    @Override
                    public void run() {
                        handleRequest(connection);
                    }
                };
    
                exec.execute(task);
            }
    
        }
    }
    

  • 스레드의 중단 및 종료 처리
    - 1_동기화
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    // 스레드의 중단 및 종료 처리
    // 1. 스레드를 만들고 시작하는 것은 쉬운 일이다.
    // 2. 스레드를 안전하고 빠르게 멈추게 하는 것은 어렵다.
    // 3. 자바의 스레드를 멈추는 기능이 폐기되었다.
    //   Thread.stop() / Thread.suspend()
    
    // 4. 스레드를 강제로 종료하면 공유되어 있는 여러 가지 상태가
    //    망가질 수 있다.
    // 5. 스레드를 멈춰달라는 요청이 오면 진행 중이던 모든 작업을 정리한 후
    //    스스로 종료하도록 만들어야 한다.
    
    
    import java.util.concurrent.TimeUnit;
    
    
    /*public class Example4 {
        private static boolean stopRequested = false;
        // 문제점 : main 스레드가 변경한 stopRequested 의 새로운 값을 background
        //        thread 가 관측할 수 없다.
        public static void main(String[] args) throws InterruptedException {
            Thread backgroundThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    long i = 0;
                    while (!stopRequested) {
                        i++;
                        //System.out.println("value - "  + i);
                    }
                }
            });
    
            backgroundThread.start();
    
            TimeUnit.SECONDS.sleep(3);
            stopRequested = true;
        }
    
    }
    */
    
    // 문제점 : main 스레드가 변경한 stopRequested 의 새로운 값을 background
    //        thread 가 관측할 수 없다.
    
    // 해결책 1. 동기화(synchronization)
    // 1) 상호 배제 : 다른 스레드가 변경 중인 객체의 상태를 관측할 수 없도록 해준다. - 락
    // 2) 동기화는 동기화 메소드 또는 동기화 블록에 의해 진입한 스레드가 동일한 락의 보호 아래
    //    이루어진 모든 변경을 관측할 수 있다.
    
    // 문제점 : 순환문의 각 단계마다 동기화를 실행하는 비용이 크다.
    
    
    public class Example4 {
        private static synchronized void stop() { stopRequested = true; }
        private static synchronized boolean isStopRequested() {
            return stopRequested;
        }
    
        private static boolean stopRequested = false;
    
        public static void main(String[] args) throws InterruptedException {
            Thread backgroundThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    long i = 0;
                    while (!isStopRequested()) {
                        i++;
                        System.out.println("value - "  + i);
                    }
                }
            });
    
            backgroundThread.start();
    
            TimeUnit.SECONDS.sleep(3);
            stop();
            stopRequested = true;
        }
    }
    

  • - 2_cv 제한자 (const-volatile)
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    // 스레드의 중단 및 종료 처리
    // 1. 스레드를 만들고 시작하는 것은 쉬운 일이다.
    // 2. 스레드를 안전하고 빠르게 멈추게 하는 것은 어렵다.
    // 3. 자바의 스레드를 멈추는 기능이 폐기되었다.
    //   Thread.stop() / Thread.suspend()
    
    // 4. 스레드를 강제로 종료하면 공유되어 있는 여러 가지 상태가
    //    망가질 수 있다.
    // 5. 스레드를 멈춰달라는 요청이 오면 진행 중이던 모든 작업을 정리한 후
    //    스스로 종료하도록 만들어야 한다.
    
    import java.util.concurrent.TimeUnit;
    
    // cv 제한자(const-volatile) : C, C++, C# - Debug / [Release]
    
    // 해결책 2. volatile
    // 정의 : 컴파일러가 최적화를 하지 말라는 지시어 (내부에 cache하여 사용하지 않도록 막음.)
    //   멀티 스레드 상에서 공유되는 변수, 하드웨어와 연결된 변수 사용시
    //   되도록 volatile 붙이자.
    // 효과 : 어떤 스레드건 가장 최근에 기록된 값을 읽을 수 있다.
    public class Example4 {
        private static volatile boolean stopRequested = false;
    
        public static void main(String[] args) throws InterruptedException {
            Thread backgroundThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    long i = 0;
                    while (!stopRequested) {
                        i++;
                        System.out.println("value - "  + i);
                    }
                }
            });
    
            backgroundThread.start();
    
            TimeUnit.SECONDS.sleep(3);
            stopRequested = true;
        }
    }