본문 바로가기

Language/Java

(15)
자바의 동기화 메커니즘(volatile, synchronized, Lock(ReentrantLock), Atomic/CAS, 동기화 컬렉션) volatile멀티스레드 환경에서 한 스레드가 변경한 값을 다른 스레드가 제대로 볼 수 있는지에 대한 문제를 메모리 가시성이라고 한다. 즉, 메모리에 변경한 값이 보이는가, 보이지 않는가의 문제이다. 메모리 가시성 문제는 캐시 메모리로 인해 발생한다. CPU 입장에서 메인 메모리는 거리도 멀고 속도도 상대적으로 느리다. CPU 연산은 매우 빠르기 때문에 CPU 연산의 빠른 성능을 따라가려면, CPU와 가까이 있으며 매우 빠른 메모리가 필요하다. 이것이 캐시 메모리이다. 캐시 메모리는 메인 메모리보다 CPU와 가까이 있으면서 속도도 더 빠르다. 현대의 대부분의 CPU는 각 코어마다 캐시 메모리를 갖는다. (참고로 여러 코어가 공유하는 캐시 메모리도 있다.) 예를 들어, 다음 그림처럼 main 스레드가 CP..
자바에서의 모니터(synchronized, wait() & notify(), Condition)와 생산자-소비자 문제 동기화여러 스레드가 동시에 공유 자원에 접근할 때, 접근 순서에 따라 결과 값이 달라지는 현상을 경쟁 상태(race condition)이라 한다. 이런 문제를 방지하고 데이터의 일관성과 무결성을 보장하기 위해 각 스레드가 공유 자원에 접근할 때 동시에 접근하지 못하도록 접근 순서를 제어하는 것을 동기화라고 한다. 그리고 이때 데이터 일관성을 보장하기 위해 하나의 프로세스만 접근해야 하는 영역을 임계 영역(critical section)이라 한다. 따라서 동기화는 경쟁 상태를 방지하고 여러 스레드가 임계 영역에 안전하게 접근할 수 있도록 실행 순서를 조정하는 것이다. 대표적인 동기화 기법으로 뮤텍스, 세마포어, 모니터가 있다.뮤텍스(Mutex)뮤텍스는 임계 영역에 하나의 스레드만 접근하도록 한다. 그리고 ..
ExecutorService의 스레드 풀 관리 ExecutorService의 스레드 풀 관리ExecutorService의 기본 구현체인 ThreadPoolExecutor의 생성자는 다음 속성들을 사용한다.corePoolSize: 스레드 풀에서 관리되는 기본 스레드 수maximumPoolSize: 스레드 풀에서 관리되는 최대 스레드 수keepAliveTime, TimeUnit unit: 기본 스레드 수를 초과해서 만들어진 초과 스레드가 생존할 수 있는 대기 시간, 이 시간 동안 처리하는 작업이 없으면 초과 스레드는 제거된다.BlockingQueue workQueue: 작업을 보관할 블로킹 큐corePoolSize와 maximumPoolSize의 차이를 알아보기 위해 다음 예제를 살펴보자. printState() 메서드는 스레드 풀 관련 정보를 확인하기..
ExecutorService의 우아한 종료 만약 고객의 주문을 처리하는 서버를 운영중인데, 서버를 재시작해야 한다고 가정해보자. 이때 고객의 주문을 처리하고 있는 도중에 갑자기 재시작하면 해당 고객의 주문이 제대로 진행되지 못할 것이다. 따라서 새로운 주문 요청은 막고, 이미 진행중인 주문은 모두 완료한 후에 서버를 재시작하는 것이 가장 이상적이다.  이처럼 서비스를 안정적으로 종료하는 것도 매우 중요하다. 이렇게 문제 없이 우아하게 종료하는 방식을 graceful shutdown이라고 한다.ExecutorService의 종료 메서드ExecutorService의 종료 관련 메서드는 다음과 같다.서비스 종료void shutdown()새로운 작업을 받지 않고, 이미 제출된 작업을 모두 완료한 후에 종료한다.논블로킹 메서드이다. 즉, 이 메서드를 호출..
Executor 프레임워크(ExecutorService, Callable, Future) 스레드 풀의 필요성스레드 풀을 사용하면 스레드를 재사용할 수 있어서, 스레드 생성 시간을 절약할 수 있다. 또한 생성되는 최대 스레드 수가 제한되어 무분별하게 스레드가 생성되어 자원을 낭비하는 것을 막을 수 있다. 스레드 풀을 직접 구현하려면 다음과 같은 것들을 고려해야 하기 때문에 매우 까다롭다.스레드가 처리할 작업이 없으면 스레드의 상태를 WAITING 상태로 관리하고, 작업 요청이 오면 RUNNABLE 상태로 변경해야 한다.어떤 생산자가 작업을 만들고, 스레드 풀에 있는 스레드가 소비자가 된다. 따라서 생산자-소비자 문제를 고려해야 한다.자바가 제공하는 Executor 프레임워크는 스레드 풀과 스레드를 관리해주며, Runnable의 문제점과 생산자-소비자 문제까지 해결해준다. 실무에서는 스레드를 직..
인터럽트 isInterrupted()public class ThreadStopMainV3 { public static void main(String[] args) { MyTask task = new MyTask(); Thread thread = new Thread(task, "work"); thread.start(); sleep(100); log("작업 중단 지시 thread.interrupt()"); thread.interrupt(); log("work 스레드 인터럽트 상태 1 = " + thread.isInterrupted()); } static class MyTask implements Runnable {..
자바에서 자식 메서드가 부모 메서드가 던지는 체크 예외의 하위 타입만 던질 수 있는 이유 자바의 예외 규칙자바에서 부모 클래스를 상속받거나, 인터페이스를 구현할 때 다음과 같은 예외 관련 규칙이 있다.체크 예외부모 메서드가 체크 예외를 던지지 않으면, 재정의된 자식 메서드도 체크 예외를 던질 수 없다.자식 메서드는 부모 메서드가 던질 수 있는 체크 예외의 하위 타입만 던질 수 있다.언체크(런타임) 예외예외 처리를 강제하지 않으므로 상관없이 던질 수 있다.예외 규칙의 이유런타임 예외는 제약이 없지만, 체크 예외는 위와 같은 제약이 있다. 이런 제약을 두는 이유는 무엇일까? 위와 같은 제약이 없다면 어떻게 될까? 다음 예제는 자식 클래스에서 부모 클래스가 던진 예외보다 상위 예외를 던지는 경우이다. (예제 코드일 뿐, 실제로는 컴파일 오류 발생)class Parent { void method()..
스레드의 생명주기 자바 스레드의 생명 주기는 여러 상태로 나뉘어진다.NEW (새로운 상태)스레드가 생성되고 아직 시작되지 않은 상태이다.즉, 스레드 객체(Thread)가 생성되었지만, start() 메서드가 호출되지 않은 상태이다.Runnable (실행 가능 상태)스레드가 실행될 준비가 된 상태이다.start() 메서드가 호출되면 스레드가 Runnable 상태가 된다.CPU 코어 하나 당 한 번에 하나의 스레드만 실행이 가능하기 때문에 Runnable 상태의 스레드라고 해서 반드시 실행 중인 것은 아니다. Runnable 상태에 있는 스레드는 스케줄러의 실행 대기열에 포함되어 있다가 차례로 CPU에서 실행된다.운영체제 스케줄러의 실행 대기열에 있든, CPU에서 실제 실행되고 있든 모두 Runnable 상태이다.Blocke..