동일한 자원을 공유하는 여러 쓰레드가 번갈아 동작할 떄, 어떤 task 가 먼저 실행될지 알수가 없다. 따라서 Thread, Runnable 을 무작성 생성하여 멀티쓰레드 프로그래밍을 하다보면 동시성 문제를 겪게 된다.
멀티 스레드 관리에서 concurrent API 에 대하여 알아보자
java.util.concurrent 패키지에는 인터페이스 기반의 유연한 태스크 실행 기능을 담은 실행자 프레임워크(Executor Framework)가 있다. 과거에는 단순한 작업 큐(work queue)를 만들기 위해서 수많은 코드를 작성해야 했는데, 이제는 아래와 같이 간단하게 작업 큐를 생성할 수 있다.
// 큐를 생성
ExecutorService exec = Executors.newSingleThreadExecutor();
// 실행자에 실행할 태스크(task; 작업) 을 넘기는 방법
exec.execute(runnable);
// 실행자 종료
exec.shutdown();
lock
을 사용하고 해당 인터페이스의 구현체로 ReetrantLock
을 사용한다.
다른 쓰레드들에게 우선순위가 밀려 자원을 계속해서 할당받지 못하는 쓰레드가 존재하는 상황을 starvation(기아 상태)라 부른다. 이러한 기아 상태를 해결하기 위해 공정성이 필요하다.
synchronized는 공정성을 지원하지 않는다. 반면 ReentrantLock은 생성자의 인자를 통해서 공정/불공정 설정을 할 수 있다. ReentrantLock의 생성자는 아래와 같이 정의되어 있다.
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
공정한 lock을 사용할 경우 경쟁이 발생했을 때 가장 오랫동안 기다린 쓰레드에게 lock을 제공한다.
submit().get()
ExecutorService exec = Executors.newSingleThreadExecutor();
exec.submit(() -> s.removeObserver(this)).get(); // 끝날 때까지 기다린다.
List<Future<String>> futures = exec.invokeAll(tasks);
System.out.println("All Tasks done");
exec.invokeAny(tasks);
System.out.println("Any Task done");
awaitTermination
Future<String> future = exec.submit(task);
exec.awaitTermination(10, TimeUnit.SECONDS);
ExecutorCompletionService
final int MAX_SIZE = 3;
ExecutorService executorService = Executors.newFixedThreadPool(MAX_SIZE);
ExecutorCompletionService<String> executorCompletionService = new ExecutorCompletionService<>(executorService);
List<Future<String>> futures = new ArrayList<>();
futures.add(executorCompletionService.submit(() -> "test1"));
futures.add(executorCompletionService.submit(() -> "test2"));
futures.add(executorCompletionService.submit(() -> "hello"));
for (int loopCount = 0; loopCount < MAX_SIZE; loopCount++) {
try {
String result = executorCompletionService.take().get();
System.out.println(result);
} catch (InterruptedException e) {
//
} catch (ExecutionException e) {
//
}
}
executorService.shutdown();
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
executor.scheduleAtFixedRate(() -> {
System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
.format(LocalDateTime.now()));
}, 0, 2, TimeUnit.SECONDS);
// 2019-09-30 23:11:22
// 2019-09-30 23:11:24
// 2019-09-30 23:11:26
// 2019-09-30 23:11:28
// ...