>  기사  >  Java  >  Java 프로그래밍의 스레드 풀에 대한 간략한 소개

Java 프로그래밍의 스레드 풀에 대한 간략한 소개

高洛峰
高洛峰원래의
2017-02-07 15:06:151335검색

Java 5부터 Java는 자체 스레드 풀을 제공합니다. 스레드 풀은 한 번에 지정된 수의 스레드만 실행하는 스레드 컨테이너입니다. java.util.concurrent.ThreadPoolExecutor는 그러한 스레드 풀입니다. 이는 매우 유연하지만 사용하기가 복잡합니다. 이 기사에서는 이에 대해 소개합니다.

첫 번째는 생성자입니다. 가장 간단한 생성자를 예로 들어보겠습니다.

public ThreadPoolExecutor(
      int corePoolSize,
      int maximumPoolSize,
      long keepAliveTime,
      TimeUnit unit,
      BlockingQueue<Runnable> workQueue)

는 상당히 복잡해 보입니다. 여기에 소개가 있습니다.
corePoolSize는 예약된 스레드 풀 크기를 나타냅니다.
maximumPoolSize는 스레드 풀의 최대 크기를 나타냅니다.
keepAliveTime은 유휴 스레드가 종료되는 시간 초과 기간을 나타냅니다.
unit은 keepAliveTime의 단위를 나타내는 열거형입니다.
workQueue는 작업을 저장하는 대기열을 나타냅니다.
스레드 풀의 작업 프로세스를 통해 이러한 매개변수의 의미를 이해할 수 있습니다. 스레드 풀의 작동 과정은 다음과 같습니다.

1. 스레드 풀이 처음 생성되면 스레드가 없습니다. 작업 대기열이 매개변수로 전달됩니다. 그러나 대기열에 작업이 있더라도 스레드 풀은 해당 작업을 즉시 실행하지 않습니다.

2. 작업을 추가하기 위해 Execute() 메서드를 호출하면 스레드 풀은 다음과 같이 판단합니다.
a. 실행 중인 스레드 수가 corePoolSize보다 적으면 즉시 스레드를 생성합니다. 작업을 실행하려면
b. 실행 중인 스레드 수가 corePoolSize보다 크거나 같으면 이 작업을 대기열에 넣습니다.
c. 이때 대기열이 가득 차고 실행 중인 스레드 수가 maximumPoolSize보다 작은 경우에도 작업을 실행하려면 스레드를 생성해야 합니다.
d. 실행 중인 스레드 수가 maximumPoolSize 보다 크거나 같으면 스레드 풀은 예외를 발생시키고 호출자에게 "더 이상 작업을 수락할 수 없습니다."라고 알립니다.

3. 스레드가 작업을 완료하면 실행을 위해 대기열에서 작업을 가져옵니다.

4. 스레드가 아무 작업도 하지 않고 특정 시간(keepAliveTime)을 초과하면 스레드 풀은 현재 실행 중인 스레드 수가 corePoolSize보다 큰지 판단하여 스레드를 중지합니다. 따라서 스레드 풀의 모든 작업이 완료된 후에는 결국 corePoolSize 크기로 축소됩니다.

이 과정을 통해 태스크가 먼저 추가되었다고 반드시 먼저 실행되는 것은 아니라는 점을 알 수 있습니다. queue size는 10, corePoolSize는 3, maximumPoolSize는 6이라고 가정한다. 그리고 20개의 task가 추가되면 실행 순서는 task 1, 2, 3이 먼저 실행되고, task 4~13이 실행된다. 대기열에 넣습니다. 이때 큐는 꽉 차서 14번, 15번, 16번 태스크는 즉시 실행되고, 17~20번 태스크는 예외가 발생합니다. 최종 순서는 1, 2, 3, 14, 15, 16, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13입니다. 다음은 스레드 풀을 사용하는 예입니다.

public static void main(String[] args) {
  BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
  ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 1, TimeUnit.DAYS, queue);
   
  for (int i = 0; i < 20; i++) {
    executor.execute(new Runnable() {
   
      public void run() {
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println(String.format("thread %d finished", this.hashCode()));
      }
    });
  }
  executor.shutdown();
}

이 예에 대한 설명은 다음과 같습니다.

1. 일반적으로 사용되는 인터페이스 구현 클래스에는 LinkedBlockingQueue 및 ArrayBlockingQueue가 포함됩니다. LinkedBlockingQueue를 사용하면 크기 제한이 없다는 장점이 있습니다. 이 경우 대기열이 가득 차지 않기 때문에 Execute()는 예외를 발생시키지 않으며 스레드 풀에서 실행 중인 스레드 수는 corePoolSize를 초과하지 않으므로 keepAliveTime 매개 변수는 의미가 없습니다.

2. shutdown() 메서드는 차단되지 않습니다. shutdown() 메서드를 호출하면 메인 스레드가 즉시 종료되고 모든 작업이 실행될 때까지 스레드 풀이 계속 실행됩니다. shutdown() 메서드가 호출되지 않으면 스레드 풀은 활성 상태로 유지되므로 언제든지 새 작업을 추가할 수 있습니다.

지금까지는 이 스레드 풀의 극히 일부만을 소개했습니다. ThreadPoolExecutor는 확장성이 뛰어나지만 이를 확장하기 위한 전제 조건은 작업 방법에 익숙해지는 것입니다. 이후 기사에서는 ThreadPoolExecutor 클래스를 확장하는 방법을 소개할 것입니다.

ava.util.concurrent.ThreadPoolExecutor 클래스는 풍부한 확장성을 제공합니다. 서브클래싱을 통해 동작을 사용자 정의할 수 있습니다. 예를 들어, 각 작업이 끝난 후 메시지를 인쇄하고 싶지만 작업 개체를 수정할 수 없으면 다음과 같이 작성할 수 있습니다.

ThreadPoolExecutor executor = new ThreadPoolExecutor(size, maxSize, 1, TimeUnit.DAYS, queue) {
  @Override
  protected void afterExecute(Runnable r, Throwable t) {
    System.out.println("Task finished.");
  }
};

afterExecute 메서드 외에도 ThreadPoolExecutor 클래스에는 beforeExecute( ) 및 종료() 메소드는 작업이 실행되기 전과 전체 스레드 풀이 중지된 후에 실행되도록 재정의될 수 있습니다.


除了可以添加任务执行前后的动作之外, ThreadPoolExecutor 还允许你自定义当添加任务失败后的执行策略。你可以调用线程池的 setRejectedExecutionHandler() 方法,用自定义的 RejectedExecutionHandler 对象替换现有的策略。 ThreadPoolExecutor 提供 4 个现有的策略,分别是:
ThreadPoolExecutor.AbortPolicy:表示拒绝任务并抛出异常
ThreadPoolExecutor.DiscardPolicy:表示拒绝任务但不做任何动作
ThreadPoolExecutor.CallerRunsPolicy:表示拒绝任务,并在调用者的线程中直接执行该任务
ThreadPoolExecutor.DiscardOldestPolicy:表示先丢弃任务队列中的第一个任务,然后把这个任务加进队列。
这里是一个例子:
ThreadPoolExecutor executor = new ThreadPoolExecutor(size, maxSize, 1, TimeUnit.DAYS, queue);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());

除此之外,你也可以通过实现 RejectedExecutionHandler 接口来编写自己的策略。下面是一个例子:

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 1, TimeUnit.SECONDS, queue,
    new RejectedExecutionHandler() {
      public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println(String.format("Task %d rejected.", r.hashCode()));
      }
    }
);

更多简单介绍Java编程中的线程池相关文章请关注PHP中文网!


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.