Java Concurrency in Practice, 개요

01. 개요

한 종류의 일을 순차적으로 처리하는 프로그램은 작성하기 쉽고 오류도 별로 생기지 않는다. 또 여러 종류 일을 순차적으로 처리하는 프로그램 보다 테스트하기도 쉽다. 종류별 작업마다 또는 시뮬레이션 작업의 각 요소마다 스레드를 하나씩 할당하면 마치 순차적인 작업처럼 처리할 수 있고, 스케줄링, 교차 실행되는 작업, 비동기 I/O, 자원 대기 등의 세부적인 부분과 상위의 비즈니스 로직에 해당하는 부분을 분리할 수 있다. 다시 말해 복잡하면서 비동기적인 작업 흐름을 각기 별도 스레드에서 수행되는 더 단순하고 동기적인 작업 흐름 몇 개로 나눌 수 있다. 이런 작업 흐름에서는 특정한 동기화 시점에서만 상호 작용이 발생한다.

이러한 장점은 서블릿이나 RMI ( Remote Method Invocation )와 같은 프레임웍에서 종종 활용된다. 프레임웍은 요청 관리, 스레드 생성, 로드 밸런싱, 그리고 작업 흐름 내에서 적절한 시점에 적절한 애플리케이션 컴포넌트에게 요청을 분배하는 등 상세한 부분을 처리한다.

1.2.3 단순한 비동기 이벤트 처리

여러 클라이언트 프로그램에서 소켓 연결을 받는 서버 애플리케이션의 경우 각 연결 마다 스레드를 할당하고 동기 I/O를 사용하도록 하면 개발 작업이 쉬워진다. 읽을 데이터가 없을 때 소켓에서 읽으려고 하면 애플리케이션은 추가 데이터가 들어올때까지 read 연산에서 대기한다.

지금까지의 운영체제는 하나의 프로세스가 생성할 수 있는 스레드 개수에 상대적으로 제약이 심해 최대 수백 개 정도만을 생성할 수 있다. 그러다보니 운영체제에서는 유닉스 시스템의 select나 poll 시스템 콜 처럼 효율적인 다중화 I/O 수단을 개발했고, 표준 자바 API에도 대기 상태에 들어가지 않는 I/O를 지원할 수 있도록 Java.nio 같은 패키지가 추가됐다.

1.2.4 더 빨리 반응하는 사용자 인터페이스

GUI 애플리케이션은 보통 스레드 하나로 동작했다. 메인 이벤트 루프에서 직좁 호출한 코드가 너무 오래 동안 실행되면 다음 이벤트를 처리할 수 없어 사용자 인터페이스가 멈춘 것처럼 보이기도 한다. ( freeze ) AWT나 스윙과 같은 GUI 프레임웍은 메인 이벤트 루프를 이벤트 전달 스레드로 전달했다.

1.3.1 안전성 위해 요소

스레드 안정성은 생각보다 미묘하기도 하다. 동기화를 충분히 해두지 않으면 여러 스레드에서 실행되는 연산의 순서가 때론 놀라울 만큼 예측하기가 어렵다.

1.4 스레드는 어디에나

모든 자바 프로그램은 기본적으로 스레드를 사용한다. JVM을 시작시키면 main 메소드를 실행할 주 스레드 뿐 아니라 가비지 컬렉션이나 객체 종료와 같은 JVM 내부 작업을 담당할 스레드도 생성한다. AWT와 스윙 사용자 인터페이스 프레임웍은 사용자 인터페이스의 이벤트를 관리할 스레드를 생성하며 timer는 대기중인 작업을 실행할 스레드를 생성한다.

이처럼 프레임웍은 프로그램 컴포넌트를 호출할 떄 프레임웍 내부의 스레드에서 호출하기 떄문에 자동으로 프로그램이 스레드를 활용하는 것과 동일한 효과를 준다. 컴포넌트는 언제나 프로그램 내부의 상태에 접근하기 떄문에 해당 상태에 접근하는 모든 코드 경로에 해당하는 컴포넌트 역시 스레드에 안전해야 한다.

타이머 : Timer를 사용하면 TimerTask에 지정된 작업이 프로그램이 아닌 Timer가 관리하는 스레드에서 실행되기 때문에 순차적인 프로그램을 복잡하게 만들수도 있다. 만약 기본 프로그램의 스레드가 사용하는 데이터에 timerTask의 작업이 접근하면 timertask 뿐 아니라 해당 데이터에 접근하는 다른 모든 클래스도 스레드에 안전하게 만들어야 한다. 이런 경우 가장 쉬운 방법은 TimerTask가 접근하는 객체 자체를 스레드에 안전하게 만드는 것이다. 즉 공유된 데이터 객체 내부에 스레드 안전성을 캡슐화 하는것이다.

출처 :브라이언 괴츠, 더그 리, 팀 피얼스, 조셉 보우비어, 데이빗 홈즈, 조슈아 블로쉬 공저, 『 멀티코어를 100% 활용하는 자바 병렬 프로그래밍』, 에이콘(2008.7.30), 1장 인용.