Programing/Java
Effective Java - 스레드(1)
안중환
2016. 3. 7. 13:01
Thread 설계
- Step 1. Single Thread Model
: AutoCloseable Interfacepackage 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 Modelpackage 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; } }