동시작업의 필요성이 대두된 몇가지 요인
- 자원 활용
프로그램은 때때로 입출력과 같은 외부 동작이 끝나기를 대기하는 경우가 많은데 이 대기시간이 길어지면 자원을 한 프로그램이 지배하게 된다. 매우 비효율적인 방식이므로 자원 분배시 효율성을 위해 동시작업이 필요하다. - 공정성
여러 프로그램은 사용자와 컴퓨터 자원에 대해 동일한 권한을 가질 수 있다. 즉, 한 프로그램이 끝나기를 대기하며 다음 프로그램의 실행을 제약하기 보단 작은 단위로 컴퓨터 자원을 공유하는 것이 더 효율적이다. - 편의성
때때로 여러 작업을 전부 일괄적으로 처리하는 것 보다 각기 일을 하나씩 처리하고 필요할 때 프로그램 간에 조율을 하는 소프트웨어를 여러 개 만드는 개 더 쉽고 효율적이다.
동시작업에 대한 필요성으로 인해 스레드라는 개념이 고안됐다. 스레드는 메모리, 파일 핸들과 같이 프로세스에 할당된 자원을 공유하지만 각기 별도의 프로그램 카운터, 스택, 지역변수를 갖는다. 또한 프로그램을 스레드로 분리하면 멀티 프로세서 시스템에서 자연스럽게 하드웨어 병렬성을 이용할 수 있다.
스레드는 의도적으로 조율하지 않는 한 비동기적으로 실행되며, 자신이 포함된 프로세스의 메모리 주소공간을 공유함으로 프로세스 내 모든 스레드는 같은 변수에 접근하고 같은 힙에 객체를 할당한다. 이 때문에 공유데이터 접근시 적절하게 동기화하지 않으면 다른 스레드가 사용 중인 변수를 순간적으로 수정해서 예상치 못한 결과를 얻을 수도 있다.
1.2 스레드의 이점
(1) 멀티프로세서 활용
멀티프로세서 서버와 PC가 점차 대중화 되는 추세에서 단일 스레드 프로그램은 컴퓨터 자원의 낭비를 초래한다. 프로세서 스케줄링의 기본 단위는 스레드이기 때문에 여러개의 스레드를 사용하는 프로그램은 단일 스레드보다 보다 효율적인 처리를 할 수 있다.
(2) 단순한 모델링
복잡하면서 비동기적인 작업(JOB)을 더 단순하고 동기적인 작업 흐름으로 세분화하여 복잡한 순차작업을 단순화 할 수 있다. 이런 장점은 서블릿이나 RMI와 같은 프레임웍에서 종종 활용된다. 즉 프레임웍에서는 요청관리, 스레드 생성, 로드 밸런싱, 요청분배와 같은 일을 처리하고 서블릿 개발자는 서블릿 개발에만 집중할 수 있다.
(3) 단순한 비동기 이벤트 처리
이벤트 발생시 마다 스레드를 생성 할당하고 동기화를 적절히 수행하면 개발 작업이 쉬워진다.
(4) 더 빨리 반응하는 사용자 인터페이스
GUI 어플리케이션에서 사용자 입력 이벤트를 스레드로 처리하여 더 빨리 반응하는 UI를 개발 할 수 있다.
1.3 스레드 사용의 위험성
스레드가 소수만의 난해한 주제였을 때 병렬 문제는 '고급' 주제였다. 하지만 지금은 개발자라면 대부분 스레드 안정성에 대해 잘 알아야 한다.
(1) 안정성 위해 요소
@NotThreadSafe
public class UnsafeSequence {
private int value;
/** 유일한 값을 리턴해야 한다 **/
public int getNext() {
return value++;
}
}
public class UnsafeSequence {
private int value;
/** 유일한 값을 리턴해야 한다 **/
public int getNext() {
return value++;
}
}
[예제 1.1] 경제 조건 위험성 코드
멀티스레드 프로그램이 동작하는 모습을 예측하려면 스레드가 서로 간섭하지 않도록 공유된 변수에 접근하는 시점에 적절하게 조율해야 한다. (자바에서는 공유변수 접근을 조율하기 위한 동기화 수단을 제공한다)
@ThreadSafe
public class Sequence {
@GuardedBy("this") private int value;
public synchronized int getNext() {
return value;
}
}
public class Sequence {
@GuardedBy("this") private int value;
public synchronized int getNext() {
return value;
}
}
[예제 1.2] 스레드 안전한 코드
(2) 활동성 위험
멀티스레드를 사용하면 단일 스레드 프로그램에서는 발생하지 않는 활동성(liveness)장애가 생길 수 있다. 안정성은 "잘못된 일이 생기지 않는다"는 것을 뜻하는 반면, 활동성은 "원하는 일이 결국 일어난다"는 보완적인 목표에 관한 것이다. 즉, 어떤 작업이 전혀 진전되지 못하는 상태에 빠질 때 활동성 장애가 발생했다고 한다. 예를 들면 무한 반복문을 생성하여 반복문 다음에 놓인 코드가 절대 실행되지 않는 경우를 들 수 있다. 스레드를 사용하면 활동성 관련 문제의 위험성이 더욱 높아진다. 즉 데드락, 소모상태, 라이브락 등 여러가지 활동성 장애를 일으킬 수 있다. 이런 활동성 장애 오류는 개발 초기에 파악하기가 무척 어렵다.
(3) 성능 위험
성능문제는 형편없는 서비스 시간, 반응성, 처리율, 자원 소모, 규모에 따른 확장성 등 넚은 범위의 문제들을 포괄한다. 또한 멀티 스레드 프로그램은 단일 스레드 프로그램에서 발생할 수 있는 모든 성능 위험뿐만 아니라, 스레드를 사용하기 때문에 생기는 추가 위험에도 노출된다.
스레드를 사용하면 실행 중에 어느 정보 부하가 생기는 것도 사실이다. 스레드가 많은 프로그램에서는 컨텍스트 스위칭이 더욱 빈번하여 상당한 부담이 생기며, 스레드가 데이터를 공유할 때는 동기화 수단도 사용해야 하는데 이 동기화는 컴파일러 최적화를 방해하고, 메모리 캐시를 지우거나 무효화하기도 하며 그 밖에 공유 메모리 버스에 동기화 관련 트래픽을 유발한다. 이런 모든 요인은 성능 측면에서 추가적인 손실을 유발한다.
1.4 스레드는 어디에나
현실에서 거의 모든 자바 프로그램이 멀티스레드로 동작하는 프로그램이고, 외부에서 프로그램 내부의 상태에 접근하는 과정을 적절히 조율하기 않아도 될 만큼의 기본적인 조율 기능을 프레임웍이 담당해 주지는 않는다.
프레임웍은 프로그램 컴포넌트를 호출할 때 프레임웍 내부의 스레드에서 호출하기 때문에 자동으로 프로그램이 스레드를 활용하는 것과 동일한 효괄르 준다. 컴포넌트는 언제나 프로그램 내부의 상태에 접근하기 때문에 해당 상태에 접근하는 모든 코드 경로에 해당하는 컴포넌트 역시 스레드 안전해야 한다.
아래 예시는 외부의 스레드에서 프로그램 코드를 호출하는 예시이다.
- 타이머 (Timer)
- 서블릿과 JSP
- 원격메소드호출 (Remote Method Invocation)
- 스윙과 AWT