자바 스레드 풀의 역할 및 사용 방법

서버 측 응용 프로그램 (예 : 데이터베이스 및 웹 서버)은 클라이언트의 동시성이 높고 시간이 짧은 요청을 처리해야합니다. 따라서 이러한 요청을 처리하는 데 필요한 스레드를 자주 생성하는 것은 리소스를 많이 소비하는 작업입니다. 기존의 방법은 새로운 요청을 위해 새로운 쓰레드를 생성하는 방식으로 구현하기는 쉽지만 큰 단점이 있습니다. 각 요청에 대해 새 스레드를 생성하면 스레드를 생성하고 삭제할 때 더 많은 시간과 시스템 리소스가 필요합니다. 따라서 동시에 너무 많은 쓰레드를 생성하는 JVM은 시스템 메모리 부족을 유발할 수 있으며, 이는 생성 될 쓰레드의 수, 즉 쓰레드 풀을 사용해야한다.

1. Java의 스레드 풀이 란 무엇입니까?

스레드 풀 기술은 이전에 생성 된 스레드를 사용하여 현재 작업을 수행하는 스레드 재사용 기술이며 스레드주기 오버 헤드 및 리소스 충돌에 대한 솔루션을 제공합니다. 요청이 도착했을 때 스레드가 이미 존재하기 때문에 스레드 생성 프로세스로 인한 지연이 제거되고 애플리케이션이 더 빠르게 응답 할 수 있습니다.

  • Java는 Executor 인터페이스와 하위 인터페이스 ExecutorService 및 ThreadPoolExecutor를 중심으로하는 실행기 프레임 워크를 제공합니다. Executor를 사용하면 스레드 작업을 완료하고 실행을 위해 실행자에게 제공하기 위해 Runnable 인터페이스를 구현하기 만하면됩니다.
  • 스레드 풀을 캡슐화하고 스레드의 구현 메커니즘이 아닌 특정 작업의 실현에 프로그래밍 작업을 집중하십시오.
  • 스레드 풀을 사용하려면 먼저 ExecutorService 개체를 만든 다음 작업 집합을 여기에 전달합니다. ThreadPoolExcutor 클래스는 스레드 풀 초기화 및 최대 스레드 용량을 설정할 수 있습니다.

위의 그림은 스레드 풀 초기화에 3 개의 스레드가 있고 작업 대기열에서 실행할 작업 개체가 5 개 있음을 보여줍니다.

실행자 스레드 풀 방법


고정 스레드 풀의 경우 실행기가 현재 모든 스레드를 실행하면 보류중인 작업이 대기열에 배치되고 스레드가 유휴 상태가되면 실행됩니다.

둘째, 스레드 풀 예제

다음 내용에서는 스레드 풀의 실행기를 소개합니다.

작업을 처리 할 스레드 풀을 만들기 위해 따라야 할 단계

  • 특정 작업 로직을 실행하기위한 작업 개체 생성 (Runnable 인터페이스 구현)
  • Executor를 사용하여 스레드 풀 생성 ExecutorService
  • 작업 처리를 위해 실행될 작업 객체를 ExecutorService에 넘깁니다.
  • 실행자 스레드 풀 중지
//第一步: 创建一个任务对象(实现Runnable接口),用于执行具体的任务逻辑 (Step 1) 
class Task implements Runnable {
  private String name;

  public Task(String s) {
    name = s;
  }

  // 打印任务名称并Sleep 1秒
  // 整个处理流程执行5次
  public void run() {
    try{
      for (int i = 0; i<=5; i++) {
        if (i==0) {
          Date d = new Date();
          SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
          System.out.println("任务初始化" + name +" = " + ft.format(d));
          //第一次执行的时候,打印每一个任务的名称及初始化的时间
        }
        else{
          Date d = new Date();
          SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
          System.out.println("任务正在执行" + name +" = " + ft.format(d));
          // 打印每一个任务处理的执行时间
        }
        Thread.sleep(1000);
      }
      System.out.println("任务执行完成" + name);
    } catch(InterruptedException e) {
      e.printStackTrace();
    }
  }
}

테스트 케이스

public class ThreadPoolTest {
  // 线程池里面最大线程数量
  static final int MAX_SIZE = 3;

  public static void main (String[] args) {
    // 创建5个任务
    Runnable r1 = new Task("task 1");
    Runnable r2 = new Task("task 2");
    Runnable r3 = new Task("task 3");
    Runnable r4 = new Task("task 4");
    Runnable r5 = new Task("task 5");

    // 第二步:创建一个固定线程数量的线程池,线程数为MAX_SIZE
    ExecutorService pool = Executors.newFixedThreadPool(MAX_SIZE);

    // 第三步:将待执行的任务对象交给ExecutorService进行任务处理
    pool.execute(r1);
    pool.execute(r2);
    pool.execute(r3);
    pool.execute(r4);
    pool.execute(r5);

    // 第四步:关闭线程池
    pool.shutdown();
  }
} 

실행 결과 예

任务初始化task 1 = 05:25:55
任务初始化task 2 = 05:25:55
任务初始化task 3 = 05:25:55
任务正在执行task 3 = 05:25:56
任务正在执行task 1 = 05:25:56
任务正在执行task 2 = 05:25:56
任务正在执行task 1 = 05:25:57
任务正在执行task 3 = 05:25:57
任务正在执行task 2 = 05:25:57
任务正在执行task 3 = 05:25:58
任务正在执行task 1 = 05:25:58
任务正在执行task 2 = 05:25:58
任务正在执行task 2 = 05:25:59
任务正在执行task 3 = 05:25:59
任务正在执行task 1 = 05:25:59
任务正在执行task 1 = 05:26:00
任务正在执行task 2 = 05:26:00
任务正在执行task 3 = 05:26:00
任务执行完成task 3
任务执行完成task 2
任务执行完成task 1
任务初始化task 5 = 05:26:01
任务初始化task 4 = 05:26:01
任务正在执行task 4 = 05:26:02
任务正在执行task 5 = 05:26:02
任务正在执行task 4 = 05:26:03
任务正在执行task 5 = 05:26:03
任务正在执行task 5 = 05:26:04
任务正在执行task 4 = 05:26:04
任务正在执行task 4 = 05:26:05
任务正在执行task 5 = 05:26:05
任务正在执行task 4 = 05:26:06
任务正在执行task 5 = 05:26:06
任务执行完成task 4
任务执行完成task 5

프로그램 실행 결과에서 알 수 있듯이 태스크 4 또는 태스크 5는 풀의 스레드가 유휴 상태가 될 때만 실행됩니다. 그 전에 추가 작업이 실행될 대기열에 배치됩니다.

스레드 풀은 처음 세 개의 작업을 수행하고 스레드 풀의 스레드는 작업 4와 5를 처리하기 전에 회수 및 비워집니다.

이 스레드 풀 방법을 사용하는 주요 이점 중 하나는 한 번에 10,000 개의 요청을 처리하고 싶지만 10,000 개의 스레드를 생성하지 않으려는 경우 과도한 시스템 리소스 사용으로 인한 다운 타임을 방지 할 수 있다는 것입니다. 이 방법을 사용하여 500 개의 스레드를 포함하는 스레드 풀을 만들고 스레드 풀에 500 개의 요청을 제출할 수 있습니다.
ThreadPool은 현재 최대 500 개의 스레드를 생성하여 한 번에 500 개의 요청을 처리합니다. 스레드 프로세스가 완료된 후 ThreadPool은 내부적으로 해당 스레드에 501 번째 요청을 할당하고 나머지 모든 요청에 ​​대해 동일한 작업을 계속 수행합니다. 상대적으로 부족한 시스템 리소스의 경우 스레드 풀은 프로그램의 안정적인 작동을 보장하는 효과적인 솔루션입니다.

셋, 스레드 풀 고려 사항 및 조정 사용

  1. 교착 상태 : 다중 스레드 프로그램에서 교착 상태가 발생할 수 있지만 스레드 풀은 모든 실행 스레드가 큐에서 차단 된 스레드의 실행 결과를 기다리고있어 스레드가 실행을 계속할 수 없도록하는 또 다른 교착 상태가 발생합니다.
  2. 스레드 누수 : 작업 완료시 스레드 풀의 스레드가 제대로 반환되지 않으면 스레드 누수가 발생합니다. 예를 들어, 스레드가 예외를 던지고 풀 클래스가이 예외를 포착하지 않으면 스레드가 비정상적으로 종료되고 스레드 풀의 크기가 1 씩 줄어 듭니다. 이 상황이 여러 번 반복되면 스레드 풀이 결국 비워지고 다른 작업을 수행하는 데 스레드를 사용할 수 없습니다.
  3. 잦은 스레드 회전 : 스레드 풀의 크기가 매우 크면 스레드 간의 컨텍스트 전환에 많은 시간이 낭비됩니다. 따라서 시스템 리소스가 허용하는 경우 스레드 풀이 클수록 더 좋은 것은 아닙니다.

스레드 풀 크기 최적화 : 스레드 풀의 최적 크기는 사용 가능한 프로세서 수와 처리 할 작업의 특성에 따라 다릅니다. CPU 집약적 인 작업의 경우 시스템에 N 개의 논리 처리 코어가 있다고 가정하면 최대 스레드 풀 크기가 N 또는 N + 1이면 최대 효율성을 얻을 수 있습니다. I / O 집약적 인 작업의 경우 요청 대기 시간 (W)과 서비스 처리 시간 (S)의 비율을 고려해야합니다. 최대 스레드 풀 크기는 N * (1+ W / S)입니다. 능률.

위의 요약을 독단적으로 사용하지 마시고, 어플리케이션 작업 처리 유형에 따라 유연하게 설정하고 조정해야하며 테스트 실험은 필수 불가결합니다.

2021 년에 수집 된 최신 고주파 인터뷰 질문 (모두 문서로 구성됨), mysql, netty, spring, thread, spring cloud, jvm, 소스 코드, 알고리즘 등 많은 건조물이 있습니다. 자세한 설명은 다음과 같습니다. 자세한 학습 계획, 인터뷰 질문 정렬 등 이러한 내용을 얻으려면 Q를 추가하십시오. 11604713672

추천

출처blog.csdn.net/weixin_51495453/article/details/114648365