하나의 프로그램 안에서 여러 작업 흐름을 동시에 실행하는 방식
프로그램이 일을 처리할 때 직원 1명이 일하는 구조가 단일 쓰레드
직원 여러 명이 일을 나눠서 하는 구조가 멀티쓰레드
프로그램을 실행하면 보통 프로세스가 만들어짐
프로세스 안에서 실제로 코드를 실행하는 작업 단위가 쓰레드
프로세스는 실행 중인 프로그램 전체, 쓰레드는 그 안에서 움직이는 작업자
workerCount와 queueCapacity 를 변수로 스레드 수와 큐 사이즈 정해둠
BlockingQueue<WorkItem<T>> queue = new ArrayBlockingQueue<>(queueCapacity);
queueCapacity 크기만큼의 실제 바구니(큐)를 생성
ArrayBlockingQueue는 스레드 안전(Thread-safe)한 자료구조
동작 원리 : 메인 스레드가 이 바구니에 일감을 계속 던져 넣고(공급), 일꾼(워커 스레드)들은 바구니에서 일감을 하나씩 빼서 처리함
만약 바구니가 꽉 차면 메일 스레드는 잠시 기다리고, 바구니가 텅 비면 일꿀들도 잠시 기다리는 식의 컨베이어 벨트 역할을 함
AtomicInteger processed = new AtomicInteger(0);
숫자 0부터 시작하는 카운터
일반적인 int는 여러 일꾼이 동시에 숫자가 누락될 수 있음
하지만 AtomicInteger는 내부적으로 동기화 처리가 되어 있어서 10명의 일꾼이 동시에 +1을 하더라도 절대 숫자가 꼬이지 않고 정확하게 누적 카운트를 해주는 안전한 카운터
AtomicReference<FatalStorageException> fatalError = new AtomicReference<>();
AtomicReference<RuntimeException> runtimeError = new AtomicReference<>();
여러 일꾼 중 단 한명이라도 치명적인 에러 (예 : 네트워크 드라이브 끊어짐) 를 만났을 때, 그 에러 상황을 모든 스레드에게 즉시 공유하기 위한 변수
한 일꾼이 에러를 겪고 이 변수에 에러 객체를 담아두면 메인 스레드나 다른 일꾼들이 다음 일감을 꺼내기 전에 이 변수를 슬쩍 확인해 보고 모든 작업을 즉시 중지하고 배치를 즉시 멈출 수 있게 해주는 일종의 공장 비상정지 버튼 역할을 함
위에서 준비한 설정들을 바탕으로 실제 일꾼(스레드)들을 고용하고 일을 시작시키는 부분
long batchStarted = System.nanoTime();
작업이 총 얼마나 걸렸는지 나중에 계산하기 위해 스톱워치의 시작 버튼을 누르는 역할
Thread[] workers = new Thread[workerCount];
고용할 일꾼들의 목록을 관리하기 위해 빈 명부를 만듦.
for (int i = 0; i < workerCount; i++) {
final int workerNo = i + 1;
workers[i] = new Thread(() -> consumeQueue(queue, processor, processed, fatalError, runtimeError, logPrefix),
logPrefix + "-worker-" + workerNo);
workers[i].start();
}
지정된 일꾼 수만큼 반복하면서 다음 작업을 수행
1. 번호 부여 : workerNo = i + 1; 을 통해 일꾼들에게 1번, 2번, 3번 번호표를 줌
2. 업무 할당 (new Thread(...)) :
이 블록이 끝나면 메인 스레드 1개와 일을 열심히 하고 있느 워커 스레드 N개가 동시에 돌아가게 되는 것
consumeQueue() 메서드에서 하는 일
업무 내용
while (true) {
WorkItem<T> work = queue.take();
if (work.poison) {
return;
}
if (fatalError.get() != null || runtimeError.get() != null) {
continue;
}
try {
processor.accept(work.item);
processed.incrementAndGet();
} catch (FatalStorageException e) {
fatalError.compareAndSet(null, e);
} catch (RuntimeException e) {
runtimeError.compareAndSet(null, e);
// ...
일꾼이 바구니에 일감을 꺼내서 에러가 없는지 보고 배정받은 실제 파일 처리(processOne)를 하고 성공하면 카운트를 올리고 치명적 에러가 나면 전체 스탑 버튼을 누르는 사이클