다음 편집기에서는 여러 구현 방법과 Java 스레드 풀에 대해 자주 묻는 질문에 대한 답변을 제공합니다. 편집자님이 꽤 좋다고 하셔서 지금 공유하고 참고용으로 드리고 싶습니다. 편집자를 따라가며 살펴보자.
스레드는 업무에 관여하는 경우가 많다. 예를 들어 일부 작업은 비동기 실행을 위해 스레드로 넘겨지는 경우가 많습니다. 또는 서버 프로그램은 각 요청에 대해 별도의 스레드 처리 작업을 생성합니다. 우리가 사용하는 데이터베이스 연결과 같은 스레드 외부. 이러한 생성, 파괴 또는 열기 및 닫기 작업은 시스템 성능에 큰 영향을 미칩니다. 따라서 "풀"의 유용성이 부각됩니다.
1. 스레드 풀을 사용하는 이유
3.6.1절에 소개된 구현에서는 각 고객에게 새로운 작업자 스레드가 할당됩니다. 작업자 스레드와 클라이언트 간의 통신이 종료되면 이 스레드는 소멸됩니다. 이 구현에는 다음과 같은 단점이 있습니다.
• 서버 생성 및 폐기 작업에 소요되는 오버헤드(소요된 시간 및 시스템 리소스 포함)가 큽니다. 이 항목은 설명할 필요가 없습니다. "Thread 생성 과정"을 확인하시면 됩니다. 머신 자체에서 수행되는 작업 외에도 인스턴스화 및 시작도 필요하며, 이 작업에는 모두 스택 리소스가 필요합니다.
• 활성 스레드는 스레드 생성 및 삭제에 따른 오버헤드 외에도 시스템 리소스를 소비합니다. 이는 스택 리소스의 소모가 될 것입니다. 데이터베이스 연결 수에 대한 합리적인 값을 설정하는 것도 고려 사항이 될 것입니다.
•스레드 개수가 고정되어 있고 각 스레드의 수명 주기가 긴 경우 스레드 전환도 상대적으로 고정되어 있습니다. 운영 체제마다 스위칭 주기가 다르며 일반적으로 약 20ms입니다. 여기서 언급된 전환은 jvm의 스케줄링에 따라 스레드와 기본 운영 체제 간에 CPU 사용 권한을 전송하는 것입니다. 스레드가 자주 생성되고 소멸되면 스레드가 자주 전환됩니다. 스레드가 소멸된 후에는 이미 준비된 스레드에게 사용 권한을 주어야 스레드가 실행될 기회를 얻을 수 있기 때문입니다. 이 경우 스레드 간 전환은 더 이상 시스템의 고정된 전환 주기를 따르지 않으며 스레드 전환 비용은 생성 및 파괴 비용보다 훨씬 더 큽니다.
비교적으로 말하면 스레드 풀을 사용할 때 일부 스레드가 미리 생성되며 지속적으로 작업 대기열에서 작업을 꺼낸 후 작업을 실행합니다. 작업자 스레드가 작업 실행을 마치면 작업 대기열에 있는 다른 작업을 계속 실행합니다. 장점은 다음과 같습니다.
• 생성 및 소멸 횟수가 줄어듭니다. 각 작업자 스레드를 항상 재사용할 수 있으며 여러 작업을 수행할 수 있습니다.
•시스템 용량에 따라 스레드 풀의 스레드 수를 쉽게 조정할 수 있어 과도한 시스템 리소스 소비로 인한 시스템 충돌을 방지할 수 있습니다.
2. 스레드 풀의 간단한 구현
다음은 제가 직접 작성한 간단한 스레드 풀입니다. 이 내용 역시 Java Network 프로그래밍
package thread; import java.util.LinkedList; /** * 线程池的实现,根据常规线程池的长度,最大长度,队列长度,我们可以增加数目限制实现 * @author Han */ public class MyThreadPool extends ThreadGroup{ //cpu 数量 ---Runtime.getRuntime().availableProcessors(); //是否关闭 private boolean isClosed = false; //队列 private LinkedList<Runnable> workQueue; //线程池id private static int threadPoolID; private int threadID; public MyThreadPool(int poolSize){ super("MyThreadPool."+threadPoolID); threadPoolID++; setDaemon(true); workQueue = new LinkedList<Runnable>(); for(int i = 0;i<poolSize;i++){ new WorkThread().start(); } } //这里可以换成ConcurrentLinkedQueue,就可以避免使用synchronized的效率问题 public synchronized void execute(Runnable task){ if(isClosed){ throw new IllegalStateException("连接池已经关闭..."); }else{ workQueue.add(task); notify(); } } protected synchronized Runnable getTask() throws InterruptedException { while(workQueue.size() == 0){ if(isClosed){ return null; } wait(); } return workQueue.removeFirst(); } public synchronized void close(){ if(!isClosed){ isClosed = true; workQueue.clear(); interrupt(); } } public void join(){ synchronized (this) { isClosed = true; notifyAll(); } Thread[] threads = new Thread[activeCount()]; int count = enumerate(threads); for(int i = 0;i<count;i++){ try { threads[i].join(); } catch (Exception e) { } } } class WorkThread extends Thread{ public WorkThread(){ super(MyThreadPool.this,"workThread"+(threadID++)); System.out.println("create..."); } @Override public void run() { while(!isInterrupted()){ System.out.println("run.."); Runnable task = null; try { //这是一个阻塞方法 task = getTask(); } catch (Exception e) { } if(task != null){ task.run(); }else{ break; } } } } }
스레드에서 직접 복사한 것입니다. 풀은 주로 작업 대기열과 미리 생성된 일부 스레드를 정의합니다. 실행 메소드가 호출되는 한 작업은 스레드에 제출될 수 있습니다.
작업이 없으면 새 작업이 들어와 활성화될 때까지 후속 스레드가 getTask()에서 차단됩니다.
Join과 Close를 모두 사용하여 스레드 풀을 닫을 수 있습니다. 차이점은 조인은 대기열에 있는 작업 실행을 완료하는 반면, 닫기는 즉시 대기열을 지우고 모든 작업자 스레드를 중단한다는 것입니다. close()의 Interrupt()는 ThreadGroup에 있는 하위 스레드의 해당 Interrupt()를 호출하는 것과 동일하므로 스레드가 대기 또는 절전 모드에 있을 때 InterruptException이 발생합니다.
테스트 클래스는 다음과 같습니다.
public class TestMyThreadPool { public static void main(String[] args) throws InterruptedException { MyThreadPool pool = new MyThreadPool(3); for(int i = 0;i<10;i++){ pool.execute(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.println("working..."); } }); } pool.join(); //pool.close(); } }
3. jdk 클래스 라이브러리에서 제공하는 스레드 풀
Java는 자체 구현보다 더 강력하고 효율적이며 더 나은 스레드 풀 구현을 제공합니다. 강한.
클래스 다이어그램은 다음과 같습니다.
이러한 유형의 스레드 풀에 대해서는 이미 선배님들께서 잘 설명해 주셨습니다. Baidu의 모든 Java 스레드 풀에는 매우 상세한 예제와 튜토리얼이 작성되어 있으므로 여기서는 자세히 설명하지 않겠습니다.
4. Spring 주입 스레드 풀
Spring 프레임워크를 사용할 때 Java에서 제공하는 방법을 사용하여 스레드 풀을 생성하면 멀티 스레드 응용 프로그램에서 관리하기가 매우 불편합니다. , 그리고 호환되지 않습니다. 우리는 스프링 아이디어를 사용합니다. (Spring은 정적 메소드를 통해 주입될 수 있지만)
사실 Spring 자체도 좋은 스레드 풀 구현을 제공합니다. 이 클래스를 ThreadPoolTaskExecutor라고 합니다.
Spring의 구성은 다음과 같습니다.
<bean id="executorService" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="${threadpool.corePoolSize}" /> <!-- 线程池维护线程的最少数量 --> <property name="keepAliveSeconds" value="${threadpool.keepAliveSeconds}" /> <!-- 线程池维护线程所允许的空闲时间 --> <property name="maxPoolSize" value="${threadpool.maxPoolSize}" /> <!-- 线程池维护线程的最大数量 --> <property name="queueCapacity" value="${threadpool.queueCapacity}" /> <!-- 线程池所使用的缓冲队列 --> </bean>
5. 스레드 풀 사용 시 주의 사항
•Deadlock
모든 멀티 스레드 프로그램은 종료됩니다. 잠금 위험의 가장 간단한 경우는 두 개의 스레드 AB가 잠금 1을 보유하고 잠금 2를 요청하고 B가 잠금 2를 보유하고 잠금 1을 요청하는 것입니다. (이러한 상황은 mysql의 단독 잠금에서도 발생하며 데이터베이스에서 직접 오류 메시지를 보고합니다.) 스레드 풀에는 또 다른 종류의 교착 상태가 있습니다. 스레드 풀의 모든 작업자 스레드가 해당 작업을 실행하는 동안 차단되었다고 가정하면 특정 작업 A의 실행 결과를 기다리고 있습니다. 그러나 작업 A는 대기열에 있고 유휴 스레드가 없기 때문에 실행할 수 없습니다. 이런 방식으로 스레드 풀의 모든 리소스가 영원히 차단되고 교착 상태가 발생합니다.
•시스템 리소스 부족
스레드 풀의 스레드 수가 매우 많으면 해당 스레드가 메모리 및 기타 시스템 리소스를 포함한 많은 양의 리소스를 소비하여 시스템 성능에 심각한 영향을 미칩니다. .
•동시성 오류
스레드 풀의 작업 대기열은 작업자 스레드가 제 시간에 작업을 얻을 수 있도록 wait() 및 inform() 메서드에 의존하지만 이 두 메서드는 사용하기 어렵습니다. 코드가 잘못된 경우 알림이 손실되어 작업자 스레드가 유휴 상태로 유지되고 작업 대기열에서 처리해야 하는 작업이 무시될 수 있습니다. 좀 더 성숙한 스레드 풀을 사용하는 것이 가장 좋기 때문입니다.
• 스레드 누출
스레드 풀 사용의 심각한 위험은 스레드 누출입니다. 고정된 수의 작업자 스레드가 있는 스레드 풀의 경우 작업자 스레드가 작업을 실행할 때 RuntimeException 또는 오류를 발생시키고 이러한 예외 또는 오류가 포착되지 않으면 작업자 스레드가 비정상적으로 종료되어 스레드 풀이 영구적으로 손실됩니다. 스레드. (너무 흥미롭네요)
또 다른 상황은 작업 실행 시 작업자 스레드가 차단되는 경우입니다. 사용자가 데이터를 입력하기를 기다리고 있는데 사용자가 데이터를 입력하지 않은 경우 스레드가 차단된 것입니다. . 이러한 작업자 스레드는 이름만 존재하며 실제로는 어떤 작업도 수행하지 않습니다. 스레드 풀의 모든 스레드가 이 상태이면 스레드 풀은 새 작업을 추가할 수 없습니다.
•작업 과부하
작업자 스레드 대기열에 실행 대기 중인 작업이 많은 경우 이러한 작업 자체가 너무 많은 시스템 리소스를 소비하여 리소스 부족을 일으킬 수 있습니다.
결론적으로 스레드 풀을 사용할 때는 다음 원칙을 따라야 합니다.
1. 태스크 A가 실행 중에 태스크 B의 실행 결과를 동기적으로 기다려야 하는 경우 태스크는 A는 적합하지 않습니다. 스레드 풀의 작업 대기열에 참여하세요. A 태스크와 같이 다른 태스크의 실행 결과를 기다려야 하는 태스크를 큐에 추가하면 교착 상태가 발생할 수 있습니다
2. 특정 태스크의 실행이 차단될 수 있으며 일정 기간 동안 차단될 수 있습니다. 시간이 오래 걸리면 작업자 스레드가 영구적으로 차단되어 스레드 누출이 발생하는 것을 방지하기 위해 시간 제한을 설정해야 합니다. 서버 프로그램에서 스레드가 클라이언트의 연결을 기다리거나 클라이언트가 보낸 데이터를 기다리는 경우 차단이 발생할 수 있습니다. 다음과 같은 방법으로 시간을 설정할 수 있습니다.
setSotimeout을 호출합니다. 클라이언트가 연결될 때까지 기다리는 시간을 설정하는 ServerSocket 메서드입니다.
클라이언트에 연결된 각 소켓에 대해 소켓의 setSoTImeout 메서드를 호출하여 클라이언트가 데이터를 보낼 때까지 기다리는 시간 초과 기간을 설정합니다.
3. 작업의 특성을 이해하고 해당 작업이 자주 차단하는 io 작업을 수행하는지 아니면 차단하지 않는 작업을 수행하는지 분석합니다. 전자는 간헐적으로 CPU를 점유하는 반면, 후자는 활용도가 더 높습니다. 단기 작업이든 장기 작업이든 작업을 완료하는 데 걸리는 시간을 예측한 다음 작업의 특성에 따라 작업을 분류한 다음 작업 대기열에 다양한 유형의 작업을 추가합니다. 다양한 스레드 풀을 작업의 특성에 맞게 처리할 수 있도록 합니다. 작업의 특성, 각 스레드 풀의 할당 및 조정
4. 스레드 풀의 크기를 조정합니다. 스레드 풀의 최적 크기는 주로 시스템에서 사용 가능한 CPU 수와 작업 대기열의 작업 특성에 따라 달라집니다. N개의 CPU가 있는 시스템에 작업 대기열이 하나만 있고 모두 계산(비차단) 작업인 경우 스레드 풀에 N 또는 N+1 작업자 스레드가 있으면 일반적으로 최대 CPU 사용량을 얻습니다. 비율.
작업 대기열에 IO 작업을 수행하고 자주 차단되는 작업이 포함되어 있는 경우 모든 작업자 스레드가 항상 작동하는 것은 아니기 때문에 스레드 풀의 크기는 사용 가능한 CPU 수를 초과해야 합니다. 일반적인 작업을 선택한 다음 이 작업을 수행하는 프로젝트에서 계산을 위해 CPU가 차지하는 실제 시간에 대한 대기 시간의 비율 WT/ST를 추정합니다. N개의 CPU가 있는 시스템의 경우 CPU를 완전히 활용하려면 약 N*(1+WT/ST) 스레드를 설정해야 합니다.
물론 스레드 풀을 조정할 때 CPU 사용률만 고려해야 하는 것은 아닙니다. 스레드 풀 작업 수가 증가하면 메모리나 소켓, 파일 열기 등 기타 리소스에도 제한이 발생하게 됩니다. 데이터베이스 연결의 핸들 또는 수 등 다중 스레드가 소비하는 시스템 자원이 시스템이 감당할 수 있는 범위 내에 있는지 확인해야 합니다.
5. 작업 과부하를 피하세요. 서버는 시스템의 전송 용량을 기준으로 동시 클라이언트 연결 수를 제한해야 합니다. 클라이언트의 연결이 제한을 초과하면 서버는 연결을 거부하고 친숙한 프롬프트를 제공하거나 대기열 길이를 제한할 수 있습니다.
위 내용은 Java 스레드 풀의 여러 구현 방법과 FAQ 내용입니다. 자세한 내용은 PHP 중국어 홈페이지(www.php.cn)를 참고해주세요!