스레드는 종종 작업에 관여합니다. 예를 들어, 일부 작업은 종종 비동기 실행을 위해 스레드에 전달됩니다. 또는 서버 프로그램은 각 요청에 대해 별도의 스레드 처리 작업을 설정합니다. 우리가 사용하는 데이터베이스 연결과 같은 스레드를 넘어서 이러한 창조, 파괴 또는 개방 및 폐쇄 작업은 시스템 성능에 큰 영향을 미칩니다. 따라서 "풀"의 사용이 강조 표시됩니다.
1. 스레드 풀을 사용하는 이유
섹션 3.6.1에 설명 된 구현에서는 새 작업자 스레드가 각 고객에게 할당됩니다. 작업자 스레드가 클라이언트와 통신하면 스레드가 파괴됩니다. 이 구현 방법에는 다음과 같은 단점이 있습니다.
• 서버 생성 및 파괴 (시간 및 시스템 리소스 포함)의 오버 헤드는 매우 높습니다. 이 항목은 설명 할 필요가 없으며 "스레드 생성 프로세스"를 확인할 수 있습니다. 기계 자체가 수행 한 작업 외에도 인스턴스화 및 시작해야하며 스택 리소스의 점유가 필요합니다.
• 실을 생성하고 파괴하는 오버 헤드 외에도 활성 스레드는 시스템 리소스를 소비합니다. 이것은 스택 자원의 소비이어야합니다. 또한 데이터베이스 연결 수를 추측하는 것으로 간주됩니다.
• 스레드 수가 고정되어 있고 각 스레드에 긴 선언주기가 긴 경우 스레드 스위칭도 비교적 고정됩니다. 운영 체제마다 스위칭 사이클이 다르며 일반적으로 약 20ms가 있습니다. 여기에 언급 된 스위치는 JVM 예약에 따른 스레드와 기본 운영 체제 간의 CPU 사용 권한을 전송하는 것입니다. 스레드가 자주 생성되고 파괴되면 스레드가 파괴 된 후 스레드가 준비된 스레드에 제공되어야하므로 스레드가 실행할 수있는 기회를 얻을 수 있기 때문에 스레드가 자주 전환됩니다. 이 경우 스레드 간 전환은 시스템의 고정 스위칭 사이클을 따르지 않으며 스위치 스위치의 오버 헤드는 생성 및 파괴의 오버 헤드보다 훨씬 큽니다.
비교적 말하면 스레드 풀을 사용하여 일부 스레드가 사전 제작되어 작업 대기열에서 작업을 지속적으로 제거한 다음 작업을 실행합니다. 작업자 스레드가 하나의 작업을 완료하면 작업 대기열에서 다른 작업을 계속 실행합니다. 장점은 다음과 같습니다.
• 창조와 파괴의 수를 줄이고 각 작업자 스레드는 항상 재사용 될 수 있으며 여러 작업을 수행 할 수 있습니다.
• 과도한 시스템 리소스로 인해 시스템 충돌을 방지하기 위해 시스템의 운반 용량에 따라 스레드 풀의 스레드 수를 쉽게 조정할 수 있습니다.
2. 스레드 풀의 간단한 구현
아래는 혼자서 작성된 간단한 스레드 풀입니다.
패키지 스레드; import java.util.linkedList;/*** 일반 스레드 풀의 길이, 최대 길이 및 큐 길이에 따라 스레드 풀 구현, 구현 수를 늘릴 수 있습니다* @author han*/public class mythreadpool은 ThreadGroup {// cpu 번호 --- runtime.getRuntime ()를 확장합니다 (); // 개인 부울을 닫을지 여부는 isclosed = false; // 대기열 개인 링크드리스트 <Runnable> Workqueue; // 스레드 풀 ID 비공개 정적 int threadPoolid; 개인 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 (); }} // 여기서 동기화 된 공개 동기화 된 void execute (runnable task)를 사용하는 효율성을 피하기 위해 ConcurrentLinkedQueue로 변경할 수 있습니다. } else {workqueue.add (작업); notify (); }} 보호 된 동기화 된 runnable getTask () 던지기 중간 누락 {while (workqueue.size () == 0) {if (isclosed) {return null; } 기다리다(); } return workqueue.removeFirst (); } public synchronized void close () {if (! isclosed) {isclosed = true; workqueue.clear (); 방해하다(); }} public void join () {synchronized (this) {isclosed = true; notifyall (); } thread [] threads = 새 스레드 [activeCount ()]; int count = 열거 (스레드); for (int i = 0; i <count; i ++) {try {스레드 [i] .join (); } catch (예외 e) {}}} class workthread는 스레드 {public workthread () {super (mythreadpool.this, "workthread"+(threadid ++)); System.out.println ( "Create ..."); } @override public void run () {while (! islerrupted ()) {system.out.println ( "run .."); 실행 가능한 작업 = NULL; try {// 이것은 차단 메소드 작업 = getTask (); } catch (예외 e) {} if (task! = null) {task.run (); } else {break; }}}}}}이 스레드 풀은 주로 작업 대기열과 일부 사전 제작 된 스레드를 정의합니다. 실행 메소드가 호출되는 한 스레드에 작업을 제출할 수 있습니다.
작업이 없으면 후속 스레드는 새로운 작업이 시작되어 깨어날 때까지 GetTask ()에서 차단됩니다.
결합과 닫기는 모두 스레드 풀을 닫는 데 사용될 수 있습니다. 차이점은 Join이 대기열에서 작업을 완료하고 Close는 즉시 대기열을 지우고 모든 작업자 스레드를 방해한다는 것입니다. close ()의 인터럽트 ()는 ThreadGroup에 포함 된 자식 스레드를 포함하는 각각의 인터럽트 ()를 호출하는 것과 같습니다. 따라서 실이 대기하거나 잠을 자면 인터럽트 픽스가 발생합니다.
테스트 클래스는 다음과 같습니다.
공개 클래스 testmythreadpool {public static void main (String [] args)은 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 (InterpruptedException e) {} system.out.println ( "Working ..."); } pool.join (); //pool.close (); }}3. JDK 클래스 라이브러리에서 제공 한 스레드 풀
Java는 우수한 스레드 풀 구현을 제공하며, 이는 자체 구현보다 강력하고 효율적이며 더 강력한 기능을 가지고 있습니다.
클래스 다이어그램은 다음과 같습니다.
노인들은 이미 이러한 유형의 실 풀에 대한 좋은 설명을했습니다. Baidu의 모든 Java 스레드 풀에는 매우 자세한 예제와 튜토리얼이 작성되었으므로 여기에서 반복하지 않을 것입니다.
4. 스프링 주입 실 풀
Spring Framework를 사용하는 경우 Java가 제공 한 메소드를 사용하여 스레드 풀을 만들면 다중 스레드 애플리케이션에서 관리하는 것이 매우 불편하며 Spring 사용에 대한 아이디어에 부합하지 않습니다. (스프링은 정적 방법으로 주입 할 수 있지만)
실제로, Spring 자체는 또한 스레드 풀의 좋은 구현을 제공합니다. 이 클래스를 ThreadPooltaskexecutor라고합니다.
스프링의 구성은 다음과 같습니다.
<bean id = "executorService"> <property name = "corepoolsize"value = "$ {threadpool.corepoolsize}" /> <!-스레드 풀에서 유지 관리하는 최소 스레드 수-> <property name = "keepaliveseconds"value = "$ {ThreadPool.keepaliveseconds}"!-스레드 풀 메일 스레드가 허용됩니다. 이름 = "maxpoolsize"value = "$ {threadpool.maxpoolsize}" /> <!-스레드 풀에서 유지 관리되는 스레드의 최대 수-> <속성 이름 = "queuecapacity"value = "$ {threadpool.queeuecapacity}" /> <!-스레드 풀에서 사용하는 버퍼 queue-> < /bean>5. 스레드 풀 사용에 대한 메모
•이중 자물쇠
모든 멀티 스레드 프로그램은 교착 상태에있을 위험이 있습니다. 가장 간단한 사례는 두 개의 스레드 AB, A Holds Lock 1, 요청 잠금 2, B HOLD LOCK 2 및 요청 잠금 장치 1입니다. 스레드 풀에는 다른 교착 상태가 있습니다. 스레드 풀의 모든 작업자 스레드가 해당 작업을 실행하는 동안 차단되었다고 가정하고 특정 작업 A의 실행 결과를 기다리고 있다고 가정합니다. 작업 A는 대기열에 있으며 유휴 스레드가 없기 때문에 실행할 수 없습니다. 이런 식으로 스레드 풀의 모든 자원이 차단되고 교착 상태가 생성됩니다.
• 시스템 리소스가 불충분합니다
스레드 풀의 스레드 수가 매우 크면이 스레드는 메모리 및 기타 시스템 리소스를 포함하여 많은 양의 리소스를 소비하여 시스템 성능에 심각하게 영향을 미칩니다.
• 동시 오류
스레드 풀의 작업 대기열은 Wait () 및 Notify () 메소드에 의존하여 작업자가 적시에 작업을 얻을 수 있도록하지만이 두 가지 메소드는 사용하기가 어렵습니다. 코드가 잘못되면 알림이 손실되어 작업자 스레드가 유휴 상태로 유지되어 작업 대기열에서 처리 해야하는 작업을 무시합니다. 더 성숙한 스레드 풀을 사용하는 것이 가장 좋기 때문입니다.
• 스레드 누출
스레드 풀을 사용하는 심각한 위험은 스레드 누출입니다. 고정 수의 작업자 스레드가있는 스레드 풀의 경우 작업자 스레드가 작업을 실행할 때 runtimeexception 또는 오류를 던지고 이러한 예외 또는 오류가 잡히지 않으면 작업자 스레드는 비정상적으로 종료되어 스레드 풀이 스레드를 영구적으로 잃게됩니다. (이것은 너무 흥미 롭다)
또 다른 상황은 작업을 실행할 때 작업자 스레드가 차단된다는 것입니다. 사용자의 입력 데이터를 기다리고 있지만 사용자가 데이터를 입력하지 않으면 스레드가 항상 차단됩니다. 이러한 작업자 스레드는 이름 만이며 실제로 작업을 수행하지 않습니다. 스레드 풀의 모든 스레드 가이 상태에 있으면 스레드 풀이 새 작업을 추가 할 수 없습니다.
• 작업 과부하
작업자 스레드 큐에서 실행되도록 대기하는 많은 작업이 있으면 이러한 작업 자체가 너무 많은 시스템 리소스를 소비하고 리소스 부족을 유발할 수 있습니다.
요약하면, 스레드 풀을 사용할 때 다음과 같은 원칙을 따라야합니다.
1. 작업 A가 실행 중 작업 B의 실행 결과를 기다려야하는 경우 작업 A는 스레드 풀의 작업 대기열에 추가되기에 적합하지 않습니다. 다른 작업이 대기열에 추가 될 작업 A와 같은 결과를 실행할 때까지 기다려야하는 경우 교착 상태가 발생할 수 있습니다.
2. 작업이 차단되고 오랫동안 차단되면 작업자 스레드의 영구 차단을 피하고 스레드 누출을 유발하도록 시간 초과 시간을 설정해야합니다. 서버 프로그램에서 스레드가 클라이언트가 클라이언트가 보낸 데이터를 연결하거나 대기하기를 기다리는 경우 차단 될 수 있습니다. 다음과 같은 방법으로 시간을 설정할 수 있습니다.
SetSotimeout Serversocket 메소드에 전화하여 클라이언트가 연결 될 때까지 시간 초과 시간을 설정하십시오.
고객에게 연결된 각 소켓에 대해 소켓의 SetSotimeout 메소드를 호출하여 고객이 데이터를 보내기를 기다리는 시간 초과 시간을 설정하십시오.
3. 작업의 특성을 이해하고 작업이 종종 IO 작업을 차단하는 작업을 수행하는지 또는 차단되지 않는 작업을 수행하는지 분석하십시오. 전자는 간헐적으로 CPU를 차지하는 반면, 후자는 활용률이 높다. 작업을 완료하는 데 얼마나 걸립니까? 단기 작업입니까 아니면 장기 작업입니까? 그런 다음 작업의 특성에 따라 작업을 분류 한 다음 다른 스레드 풀의 작업 대기열에 다른 유형의 작업을 추가하십시오. 이러한 방식으로, 각 스레드 풀은 작업의 특성에 따라 할당되고 조정될 수 있습니다.
4. 스레드 풀을 크기를 조정하십시오. 스레드 풀의 최적 크기는 주로 시스템에서 사용 가능한 CPU 수와 작업 대기열의 작업 특성에 따라 다릅니다. N CPU가있는 시스템에 하나의 작업 큐가 있고,이 모든 것이 산술 특성을 가진 작업 (차단되지 않음)이면 스레드 풀에 N 또는 N+1 작업자 스레드가 있으면 최대 CPU 사용량이 일반적으로 얻어집니다.
작업 큐에 IO 작업을 수행하고 종종 차단하는 작업이 포함 된 경우 모든 작업 스레드가 항상 작동하지 않기 때문에 스레드 풀 크기가 사용 가능한 CPU 수를 초과하도록하십시오. 일반적인 작업을 선택한 다음 대기 시간과 실제로 CPU를 차지하는 시간 과이 작업을 수행하는 프로젝트에서 작업을 수행하는 시간의 비율을 추정하십시오. N CPU가있는 시스템의 경우 CPU가 완전히 활용되도록 대략 n*(1+wt/st) 스레드를 설정해야합니다.
물론, CPU 사용이 스레드 풀을 조정하는 과정에서 고려해야 할 유일한 것은 아닙니다. 스레드 풀 작업의 수가 증가함에 따라 소켓, 오픈 파일 핸들 또는 데이터베이스 연결 등과 같은 메모리 또는 기타 리소스 제한이 발생합니다. 멀티 스레드가 소비하는 시스템 리소스가 시스템 지구력의 범위 내에 있도록해야합니다.
5. 작업 과부하를 피하십시오. 서버는 시스템의 운반 용량에 따라 고객의 동시 연결 수를 제한해야합니다. 클라이언트의 연결이 한계 값을 초과하면 서버는 연결을 거부하고 친숙한 프롬프트를 만들거나 큐 길이를 제한 할 수 있습니다.
위의 몇 가지 구현 방법과 Java 스레드 풀에 대한 답변은 내가 공유 한 모든 컨텐츠입니다. 나는 당신이 당신에게 참조를 줄 수 있기를 바랍니다. 그리고 당신이 wulin.com을 더 지원할 수 있기를 바랍니다.