기본 스레드 구현: Runnable 인터페이스 활용
Runnable 인터페이스를 구현할 때 주의점: 작업 객체 인스턴스 생성은 반복문 외부에서 수행해야 합니다. 반복문 내에서 생성할 경우 락 동기화가 제대로 동작하지 않습니다.
@Controller
@RequestMapping("/task")
public class TaskProcessor implements Runnable {
private final Lock taskLock = new ReentrantLock();
private List<DataModel> dataChunk;
private int segmentCounter = 1;
public void setDataChunk(List<DataModel> dataChunk) {
this.dataChunk = dataChunk;
}
public void setSegmentCounter(int segmentCounter) {
this.segmentCounter = segmentCounter;
}
@Override
public void run() {
taskLock.lock();
try {
if(segmentCounter == 1) {
Thread.sleep(5000);
}
System.out.println("처리 데이터 수: " + dataChunk.size());
} catch (Exception e) {
System.err.println("스레드 오류: " + e.getMessage());
} finally {
taskLock.unlock();
}
}
@PostMapping("/process")
@ResponseBody
public int processData(@RequestBody List<DataModel> data) {
taskLock.lock();
try {
TaskProcessor processor = new TaskProcessor();
int batchSize = 2;
for (int i = 0; i <= data.size(); i++) {
if(i == batchSize * segmentCounter - 1 && i != data.size()) {
List<DataModel> chunk = data.subList(0, batchSize * segmentCounter);
processor.setDataChunk(chunk);
processor.setSegmentCounter(segmentCounter);
new Thread(processor).start();
segmentCounter++;
}
if(i == data.size()) {
List<DataModel> lastChunk = data.subList(
batchSize * (segmentCounter - 1), data.size());
processor.setDataChunk(lastChunk);
new Thread(processor).start();
segmentCounter++;
}
}
return segmentCounter;
} finally {
taskLock.unlock();
}
}
}
ExecutorService를 이용한 스레드 풀 관리
Runnable 상속 없이 ExecutorService로 스레드 관리:
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> {
batchProcess(dataList, user);
});
Future를 활용한 스레드 실행 결과 획득
Callable과 Future 조합으로 스레드 실행 결과 수집:
ExecutorService executor = Executors.newFixedThreadPool(5);
List<Future<ResultObject>> resultList = new ArrayList<>();
Future<ResultObject> future = executor.submit(() -> {
return processBatch(dataSegment, user, config);
});
resultList.add(future);