스레드 풀의 역할과 Java에서 스레드 풀을 사용하는 방법 설명

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

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

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

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

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

실행자 스레드 풀 메서드 | Method | Description | | --- | --- | | newFixedThreadPool (int) | 고정 된 수의 스레드를 사용하여 스레드 풀을 만듭니다. int 매개 변수는 스레드 풀의 스레드 수를 나타냅니다. | | newCachedThreadPool () | 유휴 스레드를 유연하게 회수 할 수있는 캐시 가능한 스레드 풀을 만듭니다. 유휴 스레드가 없으면 작업을 처리 할 새 스레드를 만듭니다. | | newSingleThreadExecutor () | 단일 스레드 풀 생성, 단일 작업자 스레드 만 사용하여 작업 수행 | | newScheduledThreadPool | 고정 길이 스레드 풀 생성, 타이밍 및 주기적 작업 실행 지원 |

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

둘째, 스레드 풀 예제

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

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

  1. 특정 작업 로직을 실행하기위한 작업 개체 생성 (Runnable 인터페이스 구현)
  2. Executor를 사용하여 스레드 풀 생성 ExecutorService
  3. 작업 처리를 위해 실행될 작업 객체를 ExecutorService에 넘깁니다.
  4. 실행자 스레드 풀 중지
//第一步: 创建一个任务对象(实现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)입니다. 최고의 효율성을 달성합니다.

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

내 블로그에 오신 것을 환영합니다. 많은 부티크 컬렉션이 있습니다.

  • 이 기사는 출처 표시와 함께 복제되었습니다 (연결을 첨부해야하며 텍스트 만 복제 할 수 없음) : Letter Brother 's blog .

도움이되었다고 생각하시면 좋아요를 눌러주세요! 당신의 지원은 나의 무한한 창의적 동기입니다! . 또한 저자는 최근 다음과 같은 고품질 콘텐츠를 출력하고있어 많은 관심 부탁드립니다.

추천

출처blog.csdn.net/hanxiaotongtong/article/details/112598976