12분 만에 Executor에서 Thread Pool을 처음부터 끝까지 완벽하게 이해

머리말

이전 글에서는 동시 패키지에서 일반적으로 사용되는 동기화 구성 요소에 대해 이야기하고 13분 만에 사용자 정의 동기화 구성 요소를 직접 작성했습니다.동시 패키지에서 일반적으로 사용되는 동기화 구성 요소에 대해 이야기하고 사용자 정의 동기화 구성 요소를 단계별로 구현하기도 했습니다.

이 기사에서는 동시성 패키지의 또 다른 코어인 스레드 풀에 대해 설명합니다.

이 글을 읽는 데 약 12분이 소요됩니다.

이 글을 읽기 전에, 스레드 풀에 대해 이해하고 있는지 확인하기 위해 몇 가지 질문을 살펴보겠습니다.

  1. 풀링 기술이란 무엇입니까? 어떤 기능이 있으며 어떤 시나리오에서 사용됩니까?
  2. 집행자 란 무엇입니까? 디자인 철학은 무엇인가요?
  3. 업무 유형에는 몇 가지가 있나요? 특징은 무엇입니까? 적응한 다음 Executor에게 넘겨주는 방법은 무엇입니까?
  4. 스레드 풀은 어떻게 구현되나요? 핵심 매개변수는 무엇이며 이를 구성하는 방법은 무엇입니까? 워크플로우는 무엇입니까?
  5. 스레드 풀은 예외를 어떻게 적절하게 처리합니까? 스레드 풀을 끄는 방법은 무엇입니까?
  6. 타이밍을 처리하는 스레드 풀은 어떻게 구현됩니까?

풀링 기술

스레드 생성 및 파괴로 인해 특정 오버헤드가 발생합니다.

필요할 때 여러 개의 스레드를 생성하고, 사용한 후에 소멸시키는 경우, 이러한 방식으로 스레드를 사용하면 비즈니스 프로세스가 길어질 뿐만 아니라 스레드 생성 및 소멸에 따른 오버헤드도 증가합니다.

그래서 스레드를 미리 생성해 풀(컨테이너)에서 관리하는 풀링 기술이라는 아이디어를 생각해 냈습니다.

필요한 경우 풀에서 스레드를 가져와 작업을 수행하고 실행이 완료된 후 다시 풀에 넣습니다.

쓰레드만이 풀링이라는 개념을 갖고 있는 것이 아니라, 커넥션 역시 풀링이라는 개념, 즉 커넥션 풀링이라는 개념을 갖고 있다.

풀링 기술은 자원을 재사용하고 대응력을 향상시킬 뿐만 아니라 관리도 용이하게 해줍니다.

실행자 프레임워크

실행자 프레임워크란 무엇입니까?

Executor는 일시적으로 작업 실행 방법을 정의하는 스레드 풀의 추상화로 간주될 수 있습니다.

  public interface Executor {
      void execute(Runnable command);
  }

Executor스레드 풀에서 작업 작업을 분리 하고 분리합니다.

이미지.png

작업 작업은 결과를 반환하지 않는 작업 Runnable과 결과를 반환하는 작업의 두 가지 유형으로 나뉩니다.Callable

두 작업 모두 스레드 풀에서 허용되며, 둘 다 기능적 인터페이스이고 람다 식을 사용하여 구현할 수 있습니다.

질문이 있으신 학생들도 계시겠지만, 위 프레임워크에서 정의한 실행 방식은 들어오는 작업 만 허용하는 Executor것 아닌가요 ?Runnable

Callable작업이 실행을 위해 호출하는 메서드는 무엇 인가요?

Future인터페이스는 비동기 작업의 결과를 얻는 것을 정의하는 데 사용됩니다. 구현 클래스는 종종 다음과 같습니다.FutureTask

FutureTask구현 과 동시에 Runnable필드 스토리지도 사용하며 구현Callable 시 실제로 작업을 수행합니다.RunnableCallable

스레드 풀이 Callable작업을 실행할 때 FutureTask이를 Runnable실행 으로 캡슐화하므로 (특정 소스 코드에 대해서는 나중에 설명하겠습니다) Executor실행 방법은 다음과 같습니다.Runnable

FutureTask어댑터와 동일하게 Callable변환되어 Runnable실행 됩니다.

이미지.png

Executor는 스레드 풀을 정의하며 중요한 구현은 다음과 같습니다.ThreadPoolExecutor

이를 기반 으로 ThreadPoolExecutor타이밍을 위한 스레드 풀도 있습니다.ScheduledThreadPoolExecutor

이미지.png

ThreadPoolExecutor

핵심 매개변수

ThreadPoolExecutor7개의 주요 중요한 매개변수가 있습니다.

  public ThreadPoolExecutor(int corePoolSize,
                                int maximumPoolSize,
                                long keepAliveTime,
                                TimeUnit unit,
                                BlockingQueue<Runnable> workQueue,
                                ThreadFactory threadFactory,
                                RejectedExecutionHandler handler)
  1. corePoolSize 스레드 풀의 코어 스레드 수
  2. maximumPoolSize 스레드 풀에서 생성할 수 있는 최대 스레드 수입니다.
  3. keepAliveTime 시간 초과, TimeUnit 시간 단위: 비핵심 스레드가 유휴 상태 이후 생존하는 시간
  4. workQueue는 작업 실행을 기다리는 차단 대기열을 저장합니다.
  5. threadFactory 스레드 팩토리: 스레드 생성 방법을 지정하고 비즈니스에 따라 스레드 그룹 이름을 다르게 지정할 수 있습니다.
  6. RejectedExecutionHandler 거부 전략: 스레드가 충분하지 않고 차단 대기열이 가득 찼을 때 작업을 거부하는 방법
거부 정책 효과
AbortPolicy 기본값 예외를 던진다
발신자 실행 정책 작업을 수행하기 위해 스레드를 호출합니다.
정책 폐기 처리하지 말고 폐기하세요.
가장 오래된 정책 삭제 대기열의 최신 작업을 삭제하고 현재 작업을 즉시 실행합니다.

생성 중 핵심 매개변수 외에도 스레드 풀은 내부 클래스를 사용하여 Worker스레드와 작업을 캡슐화하고 HashSet 컨테이너 workes작업 대기열을 사용하여 작업자 스레드를 저장합니다.

구현원리

흐름도

스레드 풀 구현 원리를 명확하게 이해하기 위해 먼저 순서도와 요약을 통해 원리를 설명하고 마지막으로 소스 코드 구현을 살펴봅니다.

이미지.png

  1. 작업자 스레드 수가 코어 스레드 수보다 적으면 스레드를 생성하고 작업 대기열에 합류하여 작업을 실행합니다.
  2. 작업자 스레드 수가 코어 스레드 수보다 크거나 같고 스레드 풀이 계속 실행 중인 경우 차단 대기열에 작업을 추가해 보세요.
  3. 작업이 차단 큐에 참여하지 못하고(차단 큐가 가득 찼음을 나타냄) 작업자 스레드 수가 최대 스레드 수보다 적은 경우 실행할 스레드를 생성합니다.
  4. 차단 큐가 가득 차고 작업자 스레드 수가 최대 스레드 수에 도달하면 거부 정책이 실행됩니다.
실행하다

스레드 풀에는 실행과 제출이라는 두 가지 제출 방법이 있습니다. 제출은 RunnableFuture로 캡슐화되어 최종적으로 실행됩니다.execute

      public <T> Future<T> submit(Callable<T> task) {
          if (task == null) throw new NullPointerException();
          RunnableFuture<T> ftask = newTaskFor(task);
          execute(ftask);
          return ftask;
      }

execute스레드 풀의 전체 실행 프로세스를 구현합니다.

  public void execute(Runnable command) {
      //任务为空直接抛出空指针异常
      if (command == null)
          throw new NullPointerException();
      //ctl是一个整型原子状态,包含workerCount工作线程数量 和 runState是否运行两个状态
      int c = ctl.get();
      //1.如果工作线程数 小于 核心线程数 addWorker创建工作线程
      if (workerCountOf(c) < corePoolSize) {
          if (addWorker(command, true))
              return;
          c = ctl.get();
      }
      
      // 2.工作线程数 大于等于 核心线程数时
      // 如果 正在运行 尝试将 任务加入队列
      if (isRunning(c) && workQueue.offer(command)) {
          //任务加入队列成功 检查是否运行
          int recheck = ctl.get();
          //不在运行 并且 删除任务成功 执行拒绝策略 否则查看工作线程为0就创建线程
          if (! isRunning(recheck) && remove(command))
              reject(command);
          else if (workerCountOf(recheck) == 0)
              addWorker(null, false);
      }
      // 3.任务加入队列失败,尝试去创建非核心线程,成功则结束
      else if (!addWorker(command, false))
          // 4.失败则执行拒绝策略
          reject(command);
  }
작업자 추가

addWorker작업 대기열에 참여하고 작업을 실행하기 위한 스레드를 생성하는 데 사용됩니다.

두 번째 파라미터는 코어 스레드 생성 여부를 결정하는 파라미터로, 코어 스레드가 생성되면 true, 비코어 스레드가 생성되면 false가 된다.

  private boolean addWorker(Runnable firstTask, boolean core) {
          //方便跳出双层循环
          retry:
          for (;;) {
              int c = ctl.get();
              int rs = runStateOf(c);
  
              // Check if queue empty only if necessary.
              // 检查状态
              if (rs >= SHUTDOWN &&
                  ! (rs == SHUTDOWN &&
                     firstTask == null &&
                     ! workQueue.isEmpty()))
                  return false;
  
              for (;;) {
                  int wc = workerCountOf(c);
                  //工作线程数已满 返回false 
                  if (wc >= CAPACITY ||
                      wc >= (core ? corePoolSize : maximumPoolSize))
                      return false;
                  //CAS自增工作线程数量 成功跳出双重循环
                  if (compareAndIncrementWorkerCount(c))
                      break retry;
                  //CAS失败 重新读取状态 内循环
                  c = ctl.get();  // Re-read ctl
                  if (runStateOf(c) != rs)
                      continue retry;
                  // else CAS failed due to workerCount change; retry inner loop
              }
          }
  
          //来到这里说明已经自增工作线程数量 准备创建线程
          boolean workerStarted = false;
          boolean workerAdded = false;
          Worker w = null;
          try {
              //创建worker 通过线程工厂创建线程
              w = new Worker(firstTask);
              final Thread t = w.thread;
              if (t != null) {
                  //全局锁
                  final ReentrantLock mainLock = this.mainLock;
                  mainLock.lock();
                  try {
                      // Recheck while holding lock.
                      // Back out on ThreadFactory failure or if
                      // shut down before lock acquired.
                      int rs = runStateOf(ctl.get());
  
                      if (rs < SHUTDOWN ||
                          (rs == SHUTDOWN && firstTask == null)) {
                          if (t.isAlive()) // precheck that t is startable
                              throw new IllegalThreadStateException();
                          //添加线程
                          workers.add(w);
                          int s = workers.size();
                          if (s > largestPoolSize)
                              largestPoolSize = s;
                          //标记线程添加完
                          workerAdded = true;
                      }
                  } finally {
                      mainLock.unlock();
                  }
                  //执行线程
                  if (workerAdded) {
                      t.start();
                      workerStarted = true;
                  }
              }
          } finally {
              if (! workerStarted)
                  addWorkerFailed(w);
          }
          return workerStarted;
      }

addWorkerZhonghui CAS는 작업 스레드 수를 자동으로 늘려 스레드를 생성한 후 잠그고 작업 대기열 작업(해시세트)에 스레드를 추가하며 잠금 해제 후 스레드를 시작하여 작업을 수행합니다.

runWorker

작업자에 구현된 것은 메서드 Runnable입니다 . 스레드를 시작한 후에는 계속해서 작업을 실행하고, 작업이 실행된 후에는 작업을 실행합니다.runWorker

  final void runWorker(Worker w) {
      Thread wt = Thread.currentThread();
      Runnable task = w.firstTask;
      w.firstTask = null;
      w.unlock(); // allow interrupts
      boolean completedAbruptly = true;
      try {
          //循环执行任务 getTask获取任务
          while (task != null || (task = getTask获取任务()) != null) {
              w.lock();
              // If pool is stopping, ensure thread is interrupted;
              // if not, ensure thread is not interrupted.  This
              // requires a recheck in second case to deal with
              // shutdownNow race while clearing interrupt
              if ((runStateAtLeast(ctl.get(), STOP) ||
                   (Thread.interrupted() &&
                    runStateAtLeast(ctl.get(), STOP))) &&
                  !wt.isInterrupted())
                  wt.interrupt();
              try {
                  //执行前 钩子方法
                  beforeExecute(wt, task);
                  Throwable thrown = null;
                  try {
                      //执行
                      task.run();
                  } catch (RuntimeException x) {
                      thrown = x; throw x;
                  } catch (Error x) {
                      thrown = x; throw x;
                  } catch (Throwable x) {
                      thrown = x; throw new Error(x);
                  } finally {
                      //执行后钩子方法
                      afterExecute(task, thrown);
                  }
              } finally {
                  task = null;
                  w.completedTasks++;
                  w.unlock();
              }
          }
          completedAbruptly = false;
      } finally {
          processWorkerExit(w, completedAbruptly);
      }
  }

실행 전후에 두 개의 빈 후크 메서드를 예약하여 하위 클래스가 확장할 수 있도록 남겨두고 나중에 스레드 풀 예외를 처리하는 데에도 사용됩니다.

구성 매개변수

스레드 풀에 스레드가 많을수록 더 좋습니까?

먼저 스레드를 생성할 때 오버헤드가 발생한다는 점을 이해해야 합니다. 프로그램 카운터, 가상 머신 스택, 로컬 메서드 스택은 모두 스레드 전용 공간입니다.

그리고 스레드가 공간에 적용되면 CAS를 통해 젊은 세대의 에덴 영역에 있는 메모리 조각에 적용됩니다. (동시에 적용되는 스레드가 여러 개 있을 수 있으므로 CAS가 필요합니다.)

스레드가 너무 많으면 Eden 공간을 너무 많이 사용하게 되어 Young gc가 발생하고 스레드 컨텍스트 전환에도 오버헤드가 필요합니다.

따라서 스레드 풀에 스레드가 많을수록 좋습니다. 업계는 두 가지 일반적인 솔루션으로 나뉩니다.

CPU 집약적 애플리케이션의 경우 스레드 풀은 최대 스레드 수를 CPU 코어 수 + 1로 설정하여 컨텍스트 전환을 방지하고 처리량을 향상시키며 수익을 보호하기 위해 스레드를 하나 더 남겨둡니다.

IO 집약적 상황의 경우 스레드 풀은 최대 스레드 수를 CPU 코어 수의 2배로 설정합니다. IO는 대기해야 하므로 유휴 CPU를 방지하려면 더 많은 스레드가 필요합니다.

특정 비즈니스 시나리오에는 상세한 분석이 필요하며 가장 합리적인 구성을 얻기 위해 수많은 테스트가 추가됩니다.

Executor 프레임워크는 다음과 같은 정적 팩토리 메소드를 통해 여러 스레드 풀을 제공 Executors.newSingleThreadExecutor()합니다 Executors.newFixedThreadPool().Executors.newCachedThreadPool()

그러나 다양한 비즈니스 시나리오로 인해 스레드 풀을 사용자 정의하는 것이 가장 좋으며 스레드 풀 매개변수와 구현 원리를 이해한 후에는 소스 코드를 보는 것이 어렵지 않으므로 더 자세히 설명하지 않겠습니다.

예외 처리

스레드 풀에서 예외가 발생하면 어떻게 되나요?

실행 가능

작업을 사용하면 Runnable예외가 직접 발생합니다.

         threadPool.execute(() -> {
             int i = 1;
             int j = 0;
             System.out.println(i / j);
         });

이러한 상황에 직면하면 Runnable 작업에서 try-catch를 사용하여 캡처할 수 있습니다.

         threadPool.execute(() -> {
             try {
                 int i = 1;
                 int j = 0;
                 System.out.println(i / j);
             } catch (Exception e) {
                 System.out.println(e);
             }
         });

실제 작업의 경우 콘솔에 인쇄하는 대신 로깅을 사용하세요.

호출 가능

작업을 사용할 때 Callablesubmit 메소드를 사용하면Future

         Future<Integer> future = threadPool.submit(() -> {
             int i = 1;
             int j = 0;
             return i / j;
         });

반환 값을 얻기 위해 이를 사용하지 않으면 Future.get()예외가 발생하지 않으므로 더 위험합니다.

왜 그런 상황이 있습니까?

앞서 언급했듯이 submit을 실행하면 실행 Callable으로 캡슐화 됩니다.FutureTask

Runnable 구현에서 Callable 작업을 실행할 때 예외가 발생하면 FutureTask에 캡슐화됩니다.

     public void run() {
         //...其他略
         try {
             //执行call任务
             result = c.call();
             ran = true;
         } catch (Throwable ex) {
             //出现异常 封装到FutureTask
             result = null;
             ran = false;
             setException(ex);
         }
         //..
     }

get이 실행되면 먼저 Block한 후 작업이 완료될 때까지 상태를 판단하고, 상태가 비정상인 경우 캡슐화된 예외가 발생합니다.

     private V report(int s) throws ExecutionException {
         Object x = outcome;
         if (s == NORMAL)
             return (V)x;
         if (s >= CANCELLED)
             throw new CancellationException();
         throw new ExecutionException((Throwable)x);
     }

따라서 Callable작업을 처리할 때 작업을 캡처하거나 가져올 수 있습니다.

         //捕获任务
         Future<?> f = threadPool.submit(() -> {
             try {
                 int i = 1;
                 int j = 0;
                 return i / j;
             } catch (Exception e) {
                 System.out.println(e);
             } finally {
                 return null;
             }
         });
 ​
         //捕获get
         Future<Integer> future = threadPool.submit(() -> {
             int i = 1;
             int j = 0;
             return i / j;
         });
 ​
         try {
             Integer integer = future.get();
         } catch (Exception e) {
             System.out.println(e);
         }
afterExecutor

스레드 풀을 기억하시나요 runWorker?

루프에서 차단 대기열에 있는 작업의 실행을 지속적으로 획득하고 실행 전후에 후크 메서드를 예약합니다.

실행 후 Hook 메소드를 다시 작성하기 위해 상속하고 ThreadPoolExecutor, 실행 후 예외 발생 여부를 기록하고, 예외가 있으면 이를 로그하여 은폐 계획 레이어를 생성합니다.

 public class MyThreadPool extends ThreadPoolExecutor {  
     //...
     
     @Override
     protected void afterExecute(Runnable r, Throwable t) {
         //Throwable为空 可能是submit提交 如果runnable为future 则捕获get
         if (Objects.isNull(t) && r instanceof Future<?>) {
             try {
                 Object res = ((Future<?>) r).get();
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
             } catch (ExecutionException e) {
                 t = e;
             }
         }
 ​
         if (Objects.nonNull(t)) {
             System.out.println(Thread.currentThread().getName() + ": " + t.toString());
         }
     }
 }

이렇게 하면 submit을 사용하고 get을 잊어버린 경우에도 예외가 "사라지지" 않습니다.

setUncaughtException

스레드를 생성할 때 uncaughtException스레드에서 포착되지 않은 예외가 발생할 때 호출될 포착되지 않은 예외 메서드를 설정할 수 있습니다 . 전체 정보를 보려면 로그를 인쇄할 수도 있습니다.

자체 스레드 팩토리를 정의하고, 비즈니스 그룹 그룹을 기반으로 스레드를 생성하고(오류 문제 해결을 용이하게 하기 위해) uncaughtException메소드를 설정합니다.

 public class MyThreadPoolFactory implements ThreadFactory {
 ​
     private AtomicInteger threadNumber = new AtomicInteger(1);
     
     private ThreadGroup group;
 ​
     private String namePrefix = "";
 ​
     public MyThreadPoolFactory(String group) {
         this.group = new ThreadGroup(group);
         namePrefix = group + "-thread-pool-";
     }
 ​
 ​
     @Override
     public Thread newThread(Runnable r) {
         Thread t = new Thread(group, r,
                 namePrefix + threadNumber.getAndIncrement(),
                 0);
         t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
             @Override
             public void uncaughtException(Thread t, Throwable e) {
                 System.out.println(t.getName() + ":" + e);
             }
         });
 ​
         if (t.isDaemon()) {
             t.setDaemon(false);
         }
         if (t.getPriority() != Thread.NORM_PRIORITY) {
             t.setPriority(Thread.NORM_PRIORITY);
         }
         return t;
     }
 ​
 }

스레드 풀 닫기

스레드 풀을 닫는 두 가지 방법:shutdown(),shutdownNow()

그 원칙은 다음과 같습니다. 작업 대기열 작업자의 스레드를 통과하고, 하나씩 중단하며(스레드의 interrupt메서드 호출), 중단에 응답할 수 없는 작업은 절대 종료되지 않습니다.

종료 작업이 실행됩니다.

  1. 스레드 풀 상태를 SHUTDOWN으로 설정
  2. 작업을 실행하지 않는 모든 스레드를 중단합니다.

shutdownNow 작업이 완료되지 않을 수 있습니다.

  1. 스레드 풀 상태를 STOP으로 설정
  2. 작업을 실행 중이거나 일시 중지 중인 모든 스레드를 중지해 보십시오.
  3. 실행 대기 중인 작업 목록으로 돌아가기

일반적으로 Shutdown을 사용하며, 작업을 완료할 필요가 없는 경우에는 shutdownNow를 사용할 수 있습니다.

SecheduledThreadPoolExecutor

ScheduledThreadPoolExecutor기반 으로 ThreadPoolExecutor예약된 실행 기능을 제공합니다.

두 가지 타이밍 방법이 있습니다.

scheduleAtFixedRate예를 들어 작업 시작을 주기의 시작점으로 삼으면 작업을 실행하는 데 0.5초가 걸리고 1초마다 실행됩니다. 이는 작업 완료 후 0.5초 후에 작업을 시작하는 것과 같습니다.

scheduledWithFixedDelay예를 들어 작업의 끝을 주기의 시작점으로 삼으면 작업을 실행하는 데 0.5초가 걸리고 1초마다 실행됩니다. 이는 작업이 완료된 후 1초에 작업을 시작하는 것과 같습니다.

         ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(2);
         //scheduleAtFixedRate 固定频率执行任务 周期起点为任务开始
         scheduledThreadPoolExecutor.scheduleAtFixedRate(()->{
             try {
                 TimeUnit.SECONDS.sleep(1);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("scheduleAtFixedRate 周期起点为任务开始");
             //初始延迟:1s  周期:1s
         },1,1, TimeUnit.SECONDS);
 ​
         //scheduledWithFixedDelay 固定延迟执行任务,周期起点为任务结束
         scheduledThreadPoolExecutor.scheduleWithFixedDelay(()->{
             try {
                 TimeUnit.SECONDS.sleep(1);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("scheduledWithFixedDelay 周期起点为任务结束 ");
             //初始延迟:1s  周期:1s
         },1,1, TimeUnit.SECONDS);

타이밍 스레드 풀은 지연 대기열을 사용하여 차단 대기열 역할을 합니다.

지연 큐는 예정된 작업을 정렬하여 저장하는 우선 순위 큐로, 시간이 짧을수록 더 빨리 실행됩니다.

스레드가 작업을 획득하면 지연 대기열에서 예약된 작업을 획득하고 시간이 만료되면 실행합니다.

     public RunnableScheduledFuture<?> take() throws InterruptedException {
             final ReentrantLock lock = this.lock;
             lock.lockInterruptibly();
             try {
                 for (;;) {
                     RunnableScheduledFuture<?> first = queue[0];
                     //没有定时任务 等待
                     if (first == null)
                         available.await();
                     else {
                         //获取延迟时间
                         long delay = first.getDelay(NANOSECONDS);
                         //小于等于0 说明超时,拿出来执行
                         if (delay <= 0)
                             return finishPoll(first);
                         first = null; // don't retain ref while waiting
                         //当前线程是leader则等待对应的延迟时间,再进入循环取出任务执行
                         //不是leader则一直等待,直到被唤醒
                         if (leader != null)
                             available.await();
                         else {
                             Thread thisThread = Thread.currentThread();
                             leader = thisThread;
                             try {
                                 available.awaitNanos(delay);
                             } finally {
                                 if (leader == thisThread)
                                     leader = null;
                             }
                         }
                     }
                 }
             } finally {
                 if (leader == null && queue[0] != null)
                     available.signal();
                 lock.unlock();
             }
         }

이 두 가지 타이밍 방법 중 하나는 작업의 시작을 주기의 시작점으로 사용하고, 다른 하나는 작업의 끝을 주기의 시작점으로 사용합니다.

예약된 작업을 얻는 과정은 동일하지만, 예약된 작업의 지연 시간이 다릅니다.

예약된 작업 사용의 period차이점은 기간의 시작점이 양수인 경우 작업의 시작이고, 기간의 시작점이 음수인 경우 작업의 종료라는 점입니다.

요약하다

이 기사에서는 스레드 풀에 중점을 두고 풀링 기술, 실행자, 스레드 풀 매개변수, 구성, 구현 원칙, 예외 처리, 종료 등에 대해 간단한 용어로 설명합니다.

풀링 기술을 사용하면 잦은 생성과 종료에 따른 오버헤드를 절약하고 응답 속도를 향상시키며 관리를 용이하게 할 수 있으며 스레드 풀, 커넥션 풀 등에 자주 사용됩니다.

Executor 프레임워크는 작업 작업을 실행(스레드 풀)과 분리하고 분리하며, 작업 작업은 반환 값이 없는 작업 Runnable과 반환 값이 있는 작업으로 구분됩니다.Callable

실행자는 실제로 Runnable작업만 처리하고 작업을 적응형 실행 Callable으로 캡슐화합니다.FutureTaskRunnable

스레드 풀은 작업 대기열을 사용하여 스레드를 관리합니다. 스레드가 작업을 실행한 후 실행을 위해 차단 대기열에서 작업을 가져옵니다. 비핵심 스레드가 일정 시간 동안 유휴 상태이면 닫힙니다.

스레드 풀 실행 시 작업 대기열 스레드 수가 코어 스레드 수보다 적으면 스레드를 생성하여 실행합니다(상당히 워밍업됨).

작업 대기열 스레드 수가 코어 스레드 수보다 많고 차단 대기열이 가득 차지 않은 경우 차단 대기열에 배치됩니다.

차단 대기열이 가득 차고 최대 스레드 수에 도달하지 않은 경우 비핵심 스레드가 생성되어 작업을 수행합니다.

최대 스레드 수에 도달한 경우 거부 정책 사용

구성 매개변수: CPU 집약적 유형은 CPU 코어 수 + 1이고, IO 집약적 유형은 CPU 코어 수의 2배이며 특정 구성을 테스트해야 합니다.

예외를 처리하려면 작업을 직접 캡처하거나 Callable가져오거나 스레드 풀을 상속하여 afterExecutor예외를 기록할 수 있으며, 스레드 생성 시 catch되지 않은 예외를 처리하는 방법을 설정할 수도 있습니다.

예약된 작업을 처리하는 스레드 풀은 지연 대기열로 구현됩니다. 예약된 작업이 짧을수록 더 빨리 실행됩니다. 스레드는 예약된 작업을 지연 대기열에서 가져오고(시간이 만료된 경우) 해당 시간 이전에 대기합니다. 이다.

마지막으로 (공짜로 하지 마시고 3번 연속으로 눌러 도움을 청하세요~)

이 글은 Java 동시 프로그래밍 지식 시스템을 간단한 용어로 구축하기 위해 "점에서 선으로, 선에서 표면으로" 칼럼에 포함되어 있습니다 . 관심 있는 학생들은 계속해서 관심을 가져주시기 바랍니다.

본 글의 노트와 사례는 gitee-StudyJavagithub-StudyJava 에 수록되어 있으며 , 관심 있는 학생들은 stat~에서 계속 관심을 가져주세요~

케이스 주소:

Gitee-JavaConcurrentProgramming/src/main/java/D_ThreadPool

Github-JavaConcurrentProgramming/src/main/java/D_ThreadPool

궁금한 사항은 댓글로 토론 가능하며, 까이까이의 글이 좋다고 생각하시면 좋아요, 팔로우, 모아서 응원해주시면 됩니다~

까이까이를 팔로우하고 더 유용한 정보를 공유해보세요, 공개 계정: 까이까이의 백엔드 프라이빗 주방

이 기사는 여러 기사를 게시하는 블로그인 OpenWrite 에서 게시되었습니다 !

레이쥔: 샤오미의 새로운 운영 체제인 ThePaper OS의 공식 버전이 패키징되었습니다. Gome App 복권 페이지의 팝업 창이 창업자를 모욕합니다. 미국 정부는 NVIDIA H800 GPU의 중국 수출을 제한합니다. Xiaomi ThePaper OS 인터페이스 마스터가 스크래치 를 사용하여 RISC-V 시뮬레이터를 작동시켰고 성공적으로 실행되었습니다. Linux 커널 RustDesk 원격 데스크톱 1.2.3 출시, 향상된 Wayland 지원 Logitech USB 수신기를 분리한 후 Linux 커널이 충돌했습니다. DHH "패키징 도구에 대한 날카로운 검토 ": 프런트 엔드를 전혀 구축할 필요가 없습니다(빌드 없음) JetBrains는 기술 문서를 작성하기 위해 Writerside를 출시합니다. Node.js 21용 도구 공식 출시
{{o.이름}}
{{이름}}

추천

출처my.oschina.net/u/6903207/blog/10109044