clean code appendix 1
Post on 20-May-2015
371 Views
Preview:
TRANSCRIPT
Review of Chapter-13 동시성 I
• 다중 스레드(동시성) 코드는 올바로 구현하기가 어렵다
• 동시성의 오해
• 동시성은 항상 성능을 높여준다
• 동시성을 구현해도 설계는 변하지 않는다
• 동기화 라이브러리를 사용하면 동시성을 이해할 필요가 없다
• 동시성 방어 원칙
• SRP(단일 책임 원칙): 동시성은 복잡성 하나만으로도 따로 분리할 이유가 충분
• 공유 데이터의 최소화 및 캡슐화
• 복사본을 사용
• 스레드는 가능한한 독립적으로 구현
• 동시성 구현 코드 테스트
• 완벽한 증명하기는 현실적으로 불가능
• 많은 플랫폼에서 많은 구성으로 반복해서 테스트가 필요
12년 9월 10일 월요일
클라이언트/서버 예제(1/4)
• Non-Thread Version
• 만약 성능 이슈가 발생한다면?
• 응용 프로그램의 수행시간 종류
• 프로세서 - 수치 계산, 정규 표현식 처리, 가비지 컬렉션
• I/O - 소켓 사용, 데이터 베이스 연결, 가상 메모리 스와핑 기다리기
• 멀티 스레드로 성능 개선 가능
ServerSocket serverSocket = new ServerSocket(8009);while (keepProcessing) { try { Socket socket = serverSocket.accept(); process(socket); } catch (Exception e) {
handle(e); }}
12년 9월 10일 월요일
클라이언트/서버 예제(2/4)
• Thread Version
• 이 코드의 문제점
• 하나 이상의 책임: 소켓 연결 관리, 클라이언트 처리, 스레드 정책, 서버 종료 정책
• 다양한 추상화 수준
void process(final Socket socket) { if (socket == null) return; Runnable clientHandler = new Runnable() { public void run() { try { String message = MessageUtils.getMessage(socket); MessageUtils.sendMessage(socket, "Processed: " + message); closeIgnoringException(socket); } catch (Exception e) { e.printStackTrace(); } } }; Thread clientConnection = new Thread(clientHandler); clientConnection.start();}
12년 9월 10일 월요일
클라이언트/서버 예제(3/4)
• 책임별로 클래스를 분리
• ClientConnection: 소켓 연결 관리
• ClientRequestProcessor: 클라이언트 처리
• ClientScheduler: 스레드 정책
• ConnectionManager: 서버 종료 정책
public void run() { while (keepProcessing) { try { ClientConnection clientConnection = connectionManager.awaitClient(); ClientRequestProcessor requestProcessor = new ClientRequestProcessor(clientConnection); clientScheduler.schedule(requestProcessor); } catch (Exception e) { e.printStackTrace(); } } connectionManager.shutdown();}
12년 9월 10일 월요일
클라이언트/서버 예제(4/4)
• 스레드 정책 코드 및 확장public interface ClientScheduler { public void schedule(ClientRequestProcessor requestProcessor);}public class ThreadPerRequestScheduler implements ClientScheduler { @Override public void schedule(final ClientRequestProcessor requestProcessor) { Runnable runnable = new Runnable() { public void run() { requestProcessor.process(); } }; Thread thread = new Thread(runnable); thread.start(); }}
// Java Executor framework을 사용한 Thread 정책 변경public class ExecutorClientScheduler implements ClientScheduler { Executor executor; public ExecutorClientScheduler(int availableThreads) { executor = Executors.newFixedThreadPool(availableThreads); } public void schedule(final ClientRequestProcessor requestProcessor) { Runnable runnable = new Runnable() { public void run() { requestProcessor.process(); } }; executor.execute(runnable); }}
12년 9월 10일 월요일
가능한 실행 경로(1/2)
• 2개의 스레드로 이 코드를 실행한다면?
• lastIdUsed의 초기값이 93일 경우
• 스레드 1이 94를 얻고, 스레드 2가 95를 얻고, lastIdUsed가 95가 된다
• 스레드 1이 95를 얻고, 스레드 2가 94를 얻고, lastIdUsed가 95가 된다
• 스레드 1이 94를 얻고, 스레드 2가 94를 얻고, lastIdUsed가 94가 된다
• JVM의 구현 방식에 따라 이 결과도 가능
public class IdGenerator { int lastIdUsed;
public int incrementValue() { return ++lastIdUsed; }}
12년 9월 10일 월요일
가능한 실행 경로(2/2)
• 경로의 수
• return ++lastIdUsed는 바이트 코드 8개로 구성됨
• 루프나 분기가 없이 명령 N개를 T개의 스레드로 실행했을때 가능한 경로의 수
• Ex) N = 8이고, T = 2이면 가능한 경로의 수: 12,870
• Synchronized를 적용했을 경우
• 가능한 경로의 수: 2(T)
public synchronized int incrementValue() { return ++lastIdUsed;}
12년 9월 10일 월요일
라이브러리를 이해하라(1/3)
• Executor 프레임워크
• 스레드 풀 관리
• Runnable 인터페이스 지원
• Callable/Future 인터페이스 지원
• 스레드의 수행 결과를 받아오기 위해 사용
public String processRequest(String message) throws Exception { Callable<String> makeExternalCall = new Callable<String>() { public String call() throws Exception { String result = ""; // make external request return result; } }; Future<String> result = executorService.submit(makeExternalCall); String partialResult = doSomeLocalProcessing(); return result.get() + partialResult;}
12년 9월 10일 월요일
라이브러리를 이해하라(2/3)
• 스레드를 중단하지 않는 방법
• Synchronized 및 Lock
• 비관적 잠금: 항상 락을 사용
• AtomicInteger 사용 - Concurrent package
• 낙관적 잠금: 현재 변수 값이 최종으로 알려진 값일 경우 갱신하고, 그렇지 않을 경우 성공할때까지 재 시도
• 프로세서의 CAS(Compare And Swap) 연산을 사용
public class ObjectWithValue { private int value; public synchronized void incrementValue() { ++value;} public int getValue() { return value; }}
// CAS의 구현int variableBeingSet;void simulateNonBlockingSet(int newValue) { int currentValue; do { currentValue = variableBeingSet; } while (currentValue != compareAndSwap(currentValue, newValue));}
public class ObjectWithValue { private AtomicInteger value = new AtomicInteger(); public void incrementValue() { value.incrementAndGet(); } public int getValue() { return value.get(); }
12년 9월 10일 월요일
라이브러리를 이해하라(3/3)
• 스레드에 안전하지 않은 클래스
• SimpleDateFormat, java.util 컨테이너 클래스...
• 해결 방안
• 스레드 안전 라이브러리 사용
• 직접 구현
• 클라이언트 기반 잠금 메커니즘
• 공유 데이터를 사용하는 모든 곳에서 lock을 사용
• 서버 기반 잠금 메커니즘
• 공유 데이터에 접근을 제어하는 별도의 클래스로 랩핑
12년 9월 10일 월요일
메소드 사이에 존재하는 의존성을 조심하라(1/3)
• 의존성 예제
• 여러 메소드에서 하나의 변수를 공유해서 사용
• 만약 다중 스레드가 IntegerIterator 인스턴스 하나를 공유 한다면?
public class IntegerIterator implements Iterator<Integer> { private Integer nextValue = 0;
public synchronized boolean hasNext() { return nextValue < 100000; } public synchronized Integer next() { if (nextValue == 100000) throw new IteratorPastEndException(); return nextValue++; } public synchronized Integer getNextValue() { return nextValue; }}
while(iterator.hasNext()) { int nextValue = iterator.next(); // nextValue로 뭔가를 한} 오류 발생: nextValue가 100000을 넘을 가능성이 존재
12년 9월 10일 월요일
메소드 사이에 존재하는 의존성을 조심하라(2/3)
• 해결책
• 실패를 용인
• 클라이언트 기반 잠금: 모든 클라이언트에서 동기화 로직 구현
• DRY(Don’t Repeat Yourself) 원칙 위반: 중복으로 인한 오류 발생이 쉬움
• 서버 기반 잠금
• 코드 중복이 줄어듬: 오류가 발생할 가능성이 줄어듬
• 성능이 좋아짐
• 스레드 정책이 하나임
• 공유 변수가 줄어든다
while (true) { int nextValue; synchronized (iterator) { if (!iterator.hasNext()) break; nextValue = iterator.next(); } doSometingWith(nextValue);} public class IntegerIteratorServerLocked {
private Integer nextValue = 0; public synchronized Integer getNextOrNull() { if (nextValue < 100000) return nextValue++; else return null; }}
while (true) { int nextValue = iterator.getNextOrNull();; if (next == null) break; doSometingWith(nextValue);}
Client-based
Server-based
12년 9월 10일 월요일
메소드 사이에 존재하는 의존성을 조심하라(3/3)
• 서버 코드에 손대지 못하는 경우
• Adapter 패턴을 적용
public class ThreadSafeIntegerIterator { private IntegerIterator iterator = new IntegerIterator(); public synchronized Integer getNextOrNull() { if (iterator.hasNext()) return iterator.next(); return null; }}
12년 9월 10일 월요일
작업 처리량 높이기
• EX) 네트워크에서 페이지를 읽어 분석하는 프로그램
• 가정
• 페이지를 읽어 오는 평균 I/O 시간: 1초
• 페이지를 분석하는 평균 처리시간: 0.5초
• 처리는 CPU 100% 사용, I/O는 CPU 0% 사용
• 단일 스레드 환경
• N 페이지를 처리하는 총 실행시간: 1.5초 * N
• 멀티 스레드 환경
• 3개의 스레드로 동시에 실행한다면?
• I/O 1초동안 다른 2개의 스레드는 페이지를 분석할 수 있어 단일 스레드에 비해 약 3배의 처리 속도를 가짐
12년 9월 10일 월요일
데드락(1/2)
• 4가지 조건을 모두 만족하는 경우 데드락 발생
• 상호배제(Mutual exclusion)
• 잠금&대기(Lock&Wait)
• 선점불가(No Preemption)
• 순환대기(Circular Wait)
Thread#1 Thread#2
Res2
Res1lock
lock
request
request
Thread#1
Thread#2
Res1
use
not preemptive
Thread#1
Thread#2
Res1
use
wait
Thread#1
Thread#2
Res1
use
not use
12년 9월 10일 월요일
데드락(2/2)
• 해결 방법
• 상호배제(Mutual exclusion)
• 스레드 수 이상으로 자원을 늘려서 해결 가능: 일반적으로 힘듬
• 잠금&대기(Lock&Wait)
• 필요한 모든 자원을 점유하지 못한다면 모든 자원을 반환하여 해결 가능
• 기아(Starvation), 라이브락(Livelock) 등의 문제가 발생할 수 있음
• 선점불가(No Preemption)
• 자원을 소유중인 스레드에게 해제를 요청하는 메커니즘을 사용하여 해결 가능
• 이러한 구현 및 관리가 쉽지 않음
• 순환대기(Circular Wait)
• 데드락을 방지하는 가장 흔한 전략중 하나
• 모든 자원을 똑같은 순서대로 할당하게 만들어 해결 가능
• 자원 할당 순서가 변경 불가능할 수 있음
12년 9월 10일 월요일
top related