1. 개요

java.util.concurrent에서의 패키지는 동시 응용 프로그램을 만들기위한 도구를 제공합니다.

이 기사에서는 전체 패키지에 대한 개요를 설명합니다.

2. 주요 구성품

java.util.concurrent의는 하나의 쓰기 업에서 논의하기 위해 너무 많은 기능이 포함되어 있습니다. 이 기사에서는 주로이 패키지의 가장 유용한 유틸리티 중 일부에 초점을 맞출 것입니다.

  • 집행자
  • ExecutorService
  • ScheduledExecutorService
  • 미래
  • CountDownLatch
  • CyclicBarrier
  • 신호기
  • ThreadFactory
  • BlockingQueue
  • DelayQueue
  • 자물쇠
  • 페이저

여기에서 개별 수업에 대한 많은 전용 기사를 찾을 수도 있습니다.

2.1. 집행자

Executor 는 제공된 작업을 실행하는 객체를 나타내는 인터페이스입니다.

작업이 새 스레드 또는 현재 스레드에서 실행되어야하는지 여부는 특정 구현 (호출이 시작된 위치)에 따라 다릅니다. 따라서이 인터페이스를 사용하여 실제 작업 실행 메커니즘에서 작업 실행 흐름을 분리 할 수 ​​있습니다.

여기서 주목해야 할 점은 Executor 가 작업 실행이 비동기적일 필요가 없다는 것입니다. 가장 간단한 경우 실행자는 호출 스레드에서 제출 된 작업을 즉시 호출 할 수 있습니다.

실행기 인스턴스를 만들려면 호출자를 만들어야합니다.

public class Invoker implements Executor {
    @Override
    public void execute(Runnable r) {
        r.run();
    }
}

이제이 호출자를 사용하여 작업을 실행할 수 있습니다.

public void execute() {
    Executor executor = new Invoker();
    executor.execute( () -> {
        // task to be performed
    });
}

여기서 주목할 점은 실행자가 실행을 위해 작업을 수락 할 수없는 경우 RejectedExecutionException 을 throw한다는 것 입니다.

2.2. ExecutorService

ExecutorService 는 비동기 처리를위한 완벽한 솔루션입니다. 메모리 내 대기열을 관리하고 스레드 가용성에 따라 제출 된 작업을 예약합니다.

ExecutorService 를 사용하려면 하나의 Runnable 클래스 를 만들어야 합니다.

public class Task implements Runnable {
    @Override
    public void run() {
        // task details
    }
}

이제 ExecutorService 인스턴스를 만들고이 작업을 할당 할 수 있습니다 . 생성시 스레드 풀 크기를 지정해야합니다.

ExecutorService executor = Executors.newFixedThreadPool(10);

단일 스레드 ExecutorService 인스턴스 를 생성하려는 경우 newSingleThreadExecutor (ThreadFactory threadFactory) 를 사용하여 인스턴스를 생성 할 수 있습니다 .

실행기가 생성되면이를 사용하여 작업을 제출할 수 있습니다.

public void execute() { 
    executor.submit(new Task()); 
}

태스크를 제출하는 동안 Runnable 인스턴스를 생성 할 수도 있습니다 .

executor.submit(() -> {
    new Task();
});

또한 두 가지 기본 실행 종료 방법이 제공됩니다. 첫 번째는 shutdown ()입니다 . 제출 된 모든 작업이 실행을 마칠 때까지 기다립니다. 다른 방법은 shutdownNow의 ()가 whic h는 즉시 모든 보류 / 실행 태스크를 종료한다.

또한 종료 이벤트가 트리거되거나 실행 시간 초과가 발생하거나 실행 스레드 자체가 중단 된 후 모든 작업이 실행을 완료 할 때까지 강제로 차단하는 또 다른 메서드 인 awaitTermination (long timeout, TimeUnit 단위) 이 있습니다.

try {
    executor.awaitTermination( 20l, TimeUnit.NANOSECONDS );
} catch (InterruptedException e) {
    e.printStackTrace();
}

2.3. ScheduledExecutorService

ScheduledExecutorServiceExecutorService 와 유사한 인터페이스 이지만 주기적으로 작업을 수행 할 수 있습니다.

Executor 및 ExecutorService 의 메서드는 인위적인 지연없이 그 자리에서 예약됩니다. 0 또는 음수 값은 요청을 즉시 실행해야 함을 나타냅니다.

RunnableCallable 인터페이스를 모두 사용하여 작업을 정의 할 수 있습니다 .

public void execute() {
    ScheduledExecutorService executorService
      = Executors.newSingleThreadScheduledExecutor();

    Future<String> future = executorService.schedule(() -> {
        // ...
        return "Hello world";
    }, 1, TimeUnit.SECONDS);

    ScheduledFuture<?> scheduledFuture = executorService.schedule(() -> {
        // ...
    }, 1, TimeUnit.SECONDS);

    executorService.shutdown();
}

ScheduledExecutorService주어진 고정 지연 후에 작업 예약 할 수도 있습니다 .

executorService.scheduleAtFixedRate(() -> {
    // ...
}, 1, 10, TimeUnit.SECONDS);

executorService.scheduleWithFixedDelay(() -> {
    // ...
}, 1, 10, TimeUnit.SECONDS);

여기서 scheduleAtFixedRate (Runnable command, long initialDelay, long period, TimeUnit unit) 메서드는 제공된 초기 지연 후 먼저 호출되고 이후에 서비스 인스턴스가 종료 될 때까지 지정된 기간으로 호출되는 주기적 작업을 생성하고 실행합니다.

scheduleWithFixedDelay (Runnable를 명령 긴 initialDelay의 긴 지연 TimeUnit와 부) 메소드가 실행하는 하나의 종단과의 호출들 사이의 소정 지연을 반복적으로 생성하고 제공하는 초기 지연 후 먼저 호출주기 동작을 실행하고, 다음 것.

2.4. 미래

Future 는 비동기 작업의 결과를 나타내는 데 사용됩니다. 비동기 작업이 완료되었는지 여부를 확인하고 계산 된 결과를 얻는 방법 등을 제공합니다.

또한 cancel (boolean mayInterruptIfRunning) API는 작업을 취소하고 실행중인 스레드를 해제합니다. mayInterruptIfRunning 의 값  이 true이면 작업을 실행하는 스레드가 즉시 종료됩니다.

그렇지 않으면 진행중인 작업이 완료 될 수 있습니다.

아래 코드 스 니펫을 사용하여 향후 인스턴스를 만들 수 있습니다.

public void invoke() {
    ExecutorService executorService = Executors.newFixedThreadPool(10);

    Future<String> future = executorService.submit(() -> {
        // ...
        Thread.sleep(10000l);
        return "Hello world";
    });
}

다음 코드 스 니펫을 사용하여 향후 결과가 준비되었는지 확인하고 계산이 완료되면 데이터를 가져올 수 있습니다.

if (future.isDone() && !future.isCancelled()) {
    try {
        str = future.get();
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

주어진 작업에 대한 시간 제한을 지정할 수도 있습니다. 작업이이 시간보다 오래 걸리면 TimeoutException 이 발생합니다.

try {
    future.get(10, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
    e.printStackTrace();
}

2.5. CountDownLatch

CountDownLatch ( JDK 5에 도입 됨 )는 일부 작업이 완료 될 때까지 스레드 집합을 차단하는 유틸리티 클래스입니다.

CountDownLatch를는 로 초기화되는 카운터 (정수 타입); 이 카운터는 종속 스레드가 실행을 완료함에 따라 감소합니다. 그러나 카운터가 0에 도달하면 다른 스레드가 해제됩니다.

여기에서 CountDownLatch에 대해 자세히 알아볼 수 있습니다 .

2.6. CyclicBarrier

CyclicBarrier 는 재사용 할 수 있다는 점을 제외하면 CountDownLatch 와 거의 동일하게 작동 합니다. CountDownLatch 와 달리 최종 작업을 호출하기 전에 await () 메서드 (barrier condition이라고 함 )를 사용하여 여러 스레드가 서로를 기다릴 수 있습니다 .

장벽 조건을 시작하려면 실행 가능한 태스크 인스턴스 를 만들어야합니다 .

public class Task implements Runnable {

    private CyclicBarrier barrier;

    public Task(CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    public void run() {
        try {
            LOG.info(Thread.currentThread().getName() + 
              " is waiting");
            barrier.await();
            LOG.info(Thread.currentThread().getName() + 
              " is released");
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }

}

이제 우리는 장벽 조건을 위해 경쟁하기 위해 몇 가지 스레드를 호출 할 수 있습니다.

public void start() {

    CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
        // ...
        LOG.info("All previous tasks are completed");
    });

    Thread t1 = new Thread(new Task(cyclicBarrier), "T1"); 
    Thread t2 = new Thread(new Task(cyclicBarrier), "T2"); 
    Thread t3 = new Thread(new Task(cyclicBarrier), "T3"); 

    if (!cyclicBarrier.isBroken()) { 
        t1.start(); 
        t2.start(); 
        t3.start(); 
    }
}

여기서 isBroken () 메서드는 실행 시간 동안 스레드가 중단되었는지 확인합니다. 실제 프로세스를 수행하기 전에 항상이 검사를 수행해야합니다.

2.7. 신호기

세마포어는 물리적 또는 논리적 자원의 일부에 나사 레벨 접근을 차단을 위해 사용된다. 세마포어는 허가 세트가 포함되어 있습니다; 스레드가 중요 섹션에 들어 가려고 할 때마다 허용이 사용 가능한지 여부를 세마포어를 확인해야합니다.

허용을 사용할 수없는 경우 ( tryAcquire () 를 통해 ) 스레드는 임계 섹션으로 점프 할 수 없습니다. 그러나 허용이 사용 가능하면 액세스가 허용되고 허용 카운터가 감소합니다.

실행 스레드가 임계 섹션을 해제하면 허용 카운터가 다시 증가합니다 ( release () 메서드에 의해 수행됨 ).

tryAcquire (long timeout, TimeUnit unit) 메서드 를 사용하여 액세스를 얻기위한 제한 시간을 지정할 수 있습니다 .

사용 가능한 허용 수 또는 세마포어를 얻기 위해 대기중인 스레드 수를 확인할 수도 있습니다.

다음 코드 스 니펫을 사용하여 세마포어를 구현할 수 있습니다.

static Semaphore semaphore = new Semaphore(10);

public void execute() throws InterruptedException {

    LOG.info("Available permit : " + semaphore.availablePermits());
    LOG.info("Number of threads waiting to acquire: " + 
      semaphore.getQueueLength());

    if (semaphore.tryAcquire()) {
        try {
            // ...
        }
        finally {
            semaphore.release();
        }
    }

}

Semaphore를 사용하여 데이터 구조와 같은 Mutex 를 구현할 수 있습니다 . 이에 대한 자세한 내용은 여기에서 확인할 수 있습니다.

2.8. ThreadFactory

이름에서 알 수 있듯이 ThreadFactory 는 필요에 따라 새 스레드를 생성하는 스레드 (존재하지 않는) 풀 역할을합니다. 효율적인 스레드 생성 메커니즘을 구현하기 위해 많은 상용구 코딩이 필요하지 않습니다.

ThreadFactory 를 정의 할 수 있습니다 .

public class BaeldungThreadFactory implements ThreadFactory {
    private int threadId;
    private String name;

    public BaeldungThreadFactory(String name) {
        threadId = 1;
        this.name = name;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r, name + "-Thread_" + threadId);
        LOG.info("created new thread with id : " + threadId +
            " and name : " + t.getName());
        threadId++;
        return t;
    }
}

newThread (Runnable r) 메서드를 사용하여 런타임에 새 스레드를 만들 수 있습니다.

BaeldungThreadFactory factory = new BaeldungThreadFactory( 
    "BaeldungThreadFactory");
for (int i = 0; i < 10; i++) { 
    Thread t = factory.newThread(new Task());
    t.start(); 
}

2.9. BlockingQueue

비동기 프로그래밍에서 가장 일반적인 통합 패턴 중 하나는 생산자-소비자 패턴 입니다. java.util.concurrent에서의 패키지는 데이터 구조의 노하우와 함께 제공 BlockingQueue의 이러한 비동기 시나리오에서 매우 유용 할 수 있습니다 -.

이에 대한 자세한 정보와 작업 예는 여기에서 확인할 수 있습니다 .

2.10. DelayQueue

DelayQueue 는 만료 시간 (사용자 정의 지연이라고 함)이 완료된 경우에만 요소를 가져올 수있는 요소의 무한 크기 차단 대기열입니다. 따라서 최상위 요소 ( head )가 가장 많은 지연을 가지며 마지막으로 폴링됩니다.

이에 대한 자세한 정보와 작업 예는 여기에서 확인할 수 있습니다 .

2.11. 자물쇠

당연히 Lock 은 현재 실행중인 스레드를 제외하고 다른 스레드가 특정 코드 세그먼트에 액세스하지 못하도록 차단하는 유틸리티입니다.

Lock과 Synchronized 블록의 주요 차이점은 동기화 된 블록이 메서드에 완전히 포함되어 있다는 것입니다. 그러나 Lock API의 lock () 및 unlock () 작업을 별도의 메서드로 가질 수 있습니다.

이에 대한 자세한 정보와 작업 예는 여기에서 확인할 수 있습니다 .

2.12. 페이저

PhaserCyclicBarrierCountDownLatch 보다 더 유연한 솔루션 으로, 실행을 계속하기 전에 동적 스레드 수가 기다려야하는 재사용 가능한 장벽 역할을하는 데 사용됩니다. 각 프로그램 단계에 대해 Phaser 인스턴스를 재사용하여 여러 실행 단계를 조정할 수 있습니다 .

이에 대한 자세한 정보와 작업 예는 여기에서 확인할 수 있습니다 .

3. 결론

이 고급 개요 기사에서는 java.util.concurrent 패키지에서 사용할 수있는 다양한 유틸리티에 중점을 두었습니다 .

항상 그렇듯이 전체 소스 코드는 GitHub에서 사용할 수 있습니다 .