서버 측 응용 프로그램 (예 : 데이터베이스 및 웹 서버)은 클라이언트의 동시성이 높고 시간이 짧은 요청을 처리해야합니다. 따라서 이러한 요청을 처리하는 데 필요한 스레드를 자주 생성하는 것은 리소스를 많이 소비하는 작업입니다. 기존의 방법은 새로운 요청을 위해 새로운 쓰레드를 생성하는 방식으로 구현하기는 쉽지만 큰 단점이 있습니다. 각 요청에 대해 새 스레드를 생성하면 스레드를 생성하고 삭제할 때 더 많은 시간과 시스템 리소스가 필요합니다. 따라서 동시에 너무 많은 쓰레드를 생성하는 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 씩 줄어 듭니다. 이 상황이 여러 번 반복되면 스레드 풀이 결국 비워지고 다른 작업을 수행하는 데 스레드를 사용할 수 없습니다.
- 잦은 스레드 회전 : 스레드 풀의 크기가 매우 크면 스레드 간의 컨텍스트 전환에 많은 시간이 낭비됩니다. 따라서 시스템 리소스가 허용하는 경우 스레드 풀이 클수록 더 좋은 것은 아닙니다.
스레드 풀 크기 최적화 : 스레드 풀의 최적 크기는 사용 가능한 프로세서 수와 처리 할 작업의 특성에 따라 다릅니다. CPU 집약적 인 작업의 경우 시스템에 N 개의 논리 처리 코어가 있다고 가정하면 최대 스레드 풀 크기가 N 또는 N + 1이면 최대 효율성을 얻을 수 있습니다. I / O 집약적 인 작업의 경우 요청 대기 시간 (W)과 서비스 처리 시간 (S)의 비율을 고려해야합니다. 최대 스레드 풀 크기는 N * (1+ W / S)입니다. 능률.
위의 요약을 독단적으로 사용하지 마시고, 어플리케이션 작업 처리 유형에 따라 유연하게 설정하고 조정해야하며 테스트 실험은 필수 불가결합니다.