스레드 풀의 기술적 배경
객체 지향 프로그래밍에서 객체를 만드는 데 메모리 리소스 또는 기타 더 많은 리소스가 필요하기 때문에 객체를 생성하고 파괴하는 데 시간이 걸립니다. 이것은 가상 머신이 각 객체를 추적하여 물체가 파괴 된 후 수집 할 수 있도록 각 객체를 추적하려고 시도하는 Java에서 더욱 그렇습니다.
따라서 서비스 프로그램의 효율성을 향상시키는 한 가지 방법은 물체를 생성하고 파괴하는 시간을 최소화하는 것입니다. 기존 객체를 사용하여 서빙하는 방법은 해결 해야하는 핵심 문제입니다. 실제로 이것이 일부 "풀링 리소스"기술이 생산되는 이유입니다.
예를 들어, Android에서 일반적으로 볼 수있는 많은 공통 구성 요소는 일반적으로 다양한 이미지 로딩 라이브러리 및 네트워크 요청 라이브러리와 같은 "풀"개념과 분리 할 수 없습니다. Android의 메시징 메커니즘의 Meaasge가 Meaasge.obtain ()을 사용하더라도 사용 된 Meaasge Pool의 객체 이므로이 개념은 매우 중요합니다. 이 기사에서 소개 된 스레드 풀링 기술은이 아이디어를 준수합니다.
스레드 풀의 장점 :
1. 객체 생성 및 파괴로 인한 성능 오버 헤드를 줄이기 위해 스레드 풀의 스레드를 재사용합니다.
2. 스레드의 최대 동시성 수를 효과적으로 제어하고 시스템 자원 활용을 개선하며 과도한 자원 경쟁을 피하고 막힘을 피할 수 있습니다.
3. 여러 스레드의 간단한 관리를 수행하여 스레드를 간단하고 효율적으로 수행 할 수 있습니다.
스레드 풀 프레임 워크 집행자
Java의 스레드 풀은 Executor 프레임 워크를 통해 구현됩니다. 집행자 프레임 워크에는 클래스가 포함되어 있습니다 : 집행자, 집행자, 집행자 서비스, ThreadPooleExecutor, Callable and Future, FutureTask 사용 등이 포함됩니다.
집행자 : 모든 스레드 풀 인터페이스에는 단 하나의 메소드가 있습니다.
공개 인터페이스 executor {void execute (runnable 명령); }ExecutorService : Executor의 동작 추가는 Executor 구현 클래스의 가장 직접적인 인터페이스입니다.
집행자 : 스레드 풀 생성을위한 일련의 공장 방법을 제공하고 반환 된 스레드 풀은 모두 ExecutorService 인터페이스를 구현합니다.
ThreadPooleExecutor : 스레드 풀의 특정 구현 클래스. 일반적으로 사용되는 다양한 스레드 풀은이 클래스를 기반으로 구현됩니다. 시공 방법은 다음과 같습니다.
public ThreadPooleExecutor (int corepoolsize, int maximumpoolsize, 롱 repialivetime, timeUnit 단위, blockingqueue <runnable> workqueue) {this (corepoolsize, maximumpoolsize, keepalivetime, unit, workqueue, executor.defaultthreadfactory (), defaulthandler);};CorePoolSize : 스레드 풀의 코어 스레드 수와 스레드 풀에서 실행되는 스레드 수는 CorePoolSize를 초과하지 않으며 기본적으로 생존 할 수 있습니다. allowcorethreadtimeout을 true로 설정하고 코어 스레드 수는 0이며 KeepAlivetime은 모든 스레드의 시간 초과 시간을 제어합니다.
MaxImumpOlsize : 스레드 풀에서 허용되는 최대 스레드 수;
KeepAliveTime : 유휴 스레드가 끝나는 시간 초과 시간을 말합니다.
단위 : keepalivetime의 단위를 나타내는 열거입니다.
Workqueue : 작업을 저장하는 Blockingqueue <실행 가능한 대기열을 나타냅니다.
Blockingqueue : Blockingqueue는 주로 java.util.concurrent에서 스레드 동기화를 제어하는 데 사용되는 도구입니다. 블록 큐가 비어 있으면 Blockingqueue에서 무언가를 가져 오는 작업이 차단되고 Blockingqueue가 일에 들어갈 때까지 깨어나지 않습니다. 마찬가지로, 블록 링크가 가득 차면, 물건을 저장하려는 시도는 차단되며 블록 링크에 공간이있을 때까지 작업을 계속하도록 깨어나지 않습니다. 차단 대기열은 종종 생산자와 소비자의 시나리오에 사용됩니다. 생산자는 큐에 요소를 추가하는 스레드이며 소비자는 대기열에서 요소를 가져 오는 스레드입니다. 차단 대기열은 생산자가 요소를 저장하는 컨테이너이며 소비자는 컨테이너에서만 요소를 가져옵니다. 특정 구현 클래스에는 LinkedBlockingqueue, ArrayBlockingqueued 등이 포함됩니다. 일반적으로 내부 차단 및 모닝은 잠금 및 조건 (디스플레이 잠금 및 조건의 학습 및 사용)을 통해 달성됩니다.
스레드 풀의 작업 과정은 다음과 같습니다.
스레드 풀이 처음 만들어 졌을 때 내부에는 스레드가 없었습니다. 작업 대기열은 매개 변수로 전달됩니다. 그러나 큐에 작업이 있더라도 스레드 풀이 즉시 실행되지 않습니다.
execute () 메소드를 호출하여 작업을 추가 할 때 스레드 풀은 다음과 같은 판단을합니다.
실행중인 스레드 수가 CorePoolSize보다 적은 경우이 작업을 즉시 실행할 스레드를 작성하십시오.
실행중인 스레드 수가 CorePoolSize보다 크거나 동일하면이 작업을 대기열에 넣으십시오.
현재 큐가 가득 차 있고 실행중인 스레드 수가 MaximumpOollsize보다 적은 경우, 작업을 즉시 실행하려면 비 코어 스레드를 만들어야합니다.
대기열이 가득 차 있고 실행 된 스레드 수가 MaximumpOollsize보다 크거나 동일하면 스레드 풀에 예외가 RejectecutionException을 던집니다.
스레드가 작업을 완료하면 큐에서 다음 작업이 필요합니다.
스레드가 할 일이없고 일정 기간 (KeepAlivetime)이 지나면 스레드 풀은 현재 실행중인 스레드 수가 CorePoolSize보다 크면 스레드가 중지된다고 판단합니다. 따라서 스레드 풀의 모든 작업이 완료되면 결국 CorePoolSize 크기로 줄어 듭니다.
스레드 풀의 생성 및 사용
Generation Thread Pool은 Tool Class Executor의 정적 방법을 사용합니다. 다음은 몇 가지 일반적인 스레드 풀입니다.
SingleThreadExecutor : 단일 배경 스레드 (버퍼 큐는 생존되지 않음)
public static executorService NewsingLethreadExecutor () {return new return ninalizabledElegatedExecutorService (new ThreadPooleExecutor (1, 1, 0l, timeUnit.milliseconds, new LinkedBlockingQueue <Runnable> ()); }단일 스레드 풀을 만듭니다. 이 스레드 풀에는 하나의 핵심 스레드가 작동하는데, 이는 일련의 모든 작업을 수행하는 단일 스레드와 동일합니다. 이 고유 한 스레드가 예외로 인해 종료되면 교체 할 새로운 스레드가 있습니다. 이 스레드 풀은 모든 작업의 실행 순서가 작업 제출 순서대로 실행되도록합니다.
FixedThreadPool : 고정 크기가있는 코어 스레드의 스레드 풀 만 (버퍼 큐는 무한합니다).
public static executorService newfixedthreadpool (int nthreads) {
새로운 ThreadPooleExecutor를 반환합니다 (nthreads, nthreads,
0L, TimeUnit.milliseconds,
새로운 LinkedBlockingqueue <Runnable> ());
}
고정 크기 스레드 풀을 만듭니다. 작업이 제출 될 때마다 스레드가 스레드 풀의 최대 크기에 도달 할 때까지 스레드가 생성됩니다. 스레드 풀 크기는 최대 값에 도달하면 동일하게 유지됩니다. 실행 예외로 인해 스레드가 종료되면 스레드 풀이 새 스레드를 추가합니다.
Cachedthreadpool : 무한 스레드 풀, 자동 스레드 재활용을 수행 할 수 있습니다.
public static executorService newCachedThreadPool () {return new ThreadPoolexecutor (0, integer.max_value, 60L, TimeUnit.seconds, new Synchronousqueue <Runnable> ()); }스레드 풀의 크기가 작업을 처리하는 데 필요한 스레드를 초과하면 일부 유휴 스레드 (60 초 안에 작업 실행 없음)가 재활용됩니다. 작업 횟수가 증가하면이 스레드 풀은 작업을 처리하기 위해 새 스레드를 지능적으로 추가 할 수 있습니다. 이 스레드 풀은 스레드 풀 크기를 제한하지 않으며, 이는 운영 체제 (또는 JVM)가 생성 할 수있는 최대 스레드 크기에 전적으로 의존합니다. Synchronousqueue는 1의 버퍼가있는 차단 큐입니다.
ScheduledThreadpool : 고정 된 코어 스레드 풀, 무제한 크기가있는 코어 스레드 풀. 이 스레드 풀은 주기적으로 주기적으로 작업을 수행 할 필요성을 지원합니다.
public static executorService NewsCheduledThreadPool (int corepoolsize) {return new ScheduledThreadPool (CorePoolSize, integer.max_value, default_keepalive_millis, milliseconds, new DelayedWorkqueue (); }작업을 주기적으로 수행하는 스레드 풀을 만듭니다. 유휴 상태이면 비 코어 스레드 풀은 Default_KeepaliveMillis 시간 내에 재활용됩니다.
스레드 풀에서 작업을 제출하는 가장 일반적으로 사용되는 두 가지 방법이 있습니다.
실행하다:
ExecutorService.Execute (runnable runable);
제출하다:
FutureTask task = executorService.submit (runnable runnable);
FutureTask <t> task = executorService.submit (runnable runnable, t results);
FutureTask <t> task = executorService.submit (callable <t> 호출 가능);
제출 (호출 가능 호출 가능)의 구현에도 동일하게 적용되며 동일한 적용 제출 (Runnable Runnable).
public <t> future <t> 제출 (callable <t> task) {if (task == null) 새 nullpointerexception (); FutureTask <t> ftask = newtaskfor (작업); 실행 (ftask); 반환 ftask;}제출은 결과를 반환하는 작업이며 FutureTask 객체를 반환하여 결과를 get () 메소드를 통해 얻을 수 있음을 알 수 있습니다. 최종 제출 요청은 실행 (runnable runable)입니다. Callable Object를 캡슐화하거나 FutureTask 객체로 캡슐화하십시오. FutureTask는 실행 가능하기 때문에 실행 중에 실행할 수 있습니다. Callable Objects 및 Runnable이 FutureTask 객체에 캡슐화되는 방법에 대해서는 Callable, Future, Futuretask 사용을 참조하십시오.
스레드 풀 구현의 원리
스레드 풀 사용에 대해서만 이야기하면이 블로그에는 큰 가치가 없습니다. 기껏해야 그것은 단지 집행자 관련 API에 익숙해지는 과정 일뿐입니다. 스레드 풀의 구현 프로세스는 동기화 된 키워드를 사용하지 않지만 휘발성, 잠금 및 동기 (차단) 큐, 원자 관련 클래스, FutureTask 등을 사용합니다. 후자는 성능이 향상되기 때문입니다. 이해 프로세스는 소스 코드에서 동시 제어 아이디어를 잘 배울 수 있습니다.
처음에 언급 된 스레드 풀링의 장점은 다음과 같이 요약됩니다.
스레드 재사용
최대 동시 수를 제어하십시오
스레드를 관리합니다
1. 스레드 멀티플렉싱 프로세스
스레드 멀티플렉싱의 원리를 이해하려면 먼저 스레드 수명주기를 이해해야합니다.
실의 수명주기 동안, 새로운, 달리기, 달리기, 막히고 죽은 5 개 주를 거쳐야합니다.
스레드는 새 스레드를 생성합니다. 이 프로세스는 스레드 이름, ID, 스레드가 속한 그룹 등과 같은 일부 스레드 정보를 초기화하는 것입니다. Thread 's Start ()을 호출 한 후 Java Virtual Machine은 메소드 호출 스택 및 프로그램 카운터를 생성하고 동시에 True에 HasbeenStarted 후 시작 방법을 호출 할 때 예외가 발생합니다.
이 상태의 스레드는 실행되기 시작하지 않지만 스레드가 실행될 수 있음을 의미합니다. 스레드가 실행되기 시작할 때 JVM의 스케줄러에 따라 다릅니다. 스레드가 CPU를 얻으면 run () 메소드가 호출됩니다. 스레드의 run () 메소드를 직접 호출하지 마십시오. 그런 다음 run () 메소드가 종료되거나 다른 메소드가 스레드를 중지하고 죽은 상태로 들어갈 때까지 CPU 스케줄링에 따라 준비된 블로킹 사이를 전환하십시오.
따라서 스레드 재사용을 구현하는 원리는 스레드를 살리기 위해 (준비, 실행 또는 차단)를 유지하는 것입니다. 다음으로 ThreadPooleExecutor가 스레드 재사용을 구현하는 방법을 살펴 보겠습니다.
ThreadPooleExecutor의 주요 작업자 클래스는 스레드 재사용을 제어합니다. 작업자 클래스의 단순화 된 코드를 살펴보면 이해하기 쉽습니다.
개인 최종 클래스 작업자는 Runnable {최종 스레드 스레드; Runnable FirstTask; 작업자 (runnable firsttask) {this.firsttask = firstTask; this.the.thread = getThreadFactory (). NewThread (this);} public void Run () {runworker (this);} 최종 void runworker (Workser W) {runnable task =. w.firsttask; w.firsttask = null; while (task! = null || (task = getTask ())! = null) {task.run ();}}작업자는 달리기 쉽고 동시에 실이 있습니다. 이 스레드는 열릴 스레드입니다. 새 작업자 객체를 만들 때 새 스레드 객체가 동시에 생성되고 작업자 자체가 매개 변수로 tthread로 전달됩니다. 이런 식으로, strest () 스레드 메소드가 호출되면 실제로 작업자의 run () 메소드가 실행됩니다. 그런 다음 Runworker ()에는 while 루프가있어 getTask ()에서 런닝 가능한 객체를 계속 얻고 순서대로 실행합니다. gettask ()는 어떻게 달리는 객체를 얻습니까?
여전히 단순화 된 코드 :
Private Runnable getTask () {if (일부 특별한 경우) {return null; } runnable r = workqueue.take (); return r;}이 Workqueue는 ThreadPooleExecutor를 초기화 할 때 작업을 저장하는 Blockingqueue 대기열입니다. 큐는 실행할 수있는 작업을 저장합니다. Blockingqueue는 차단 대기열이기 때문에 Blockingqueue.take.take가 비어 있고 Blockingqueue의 새 개체가 추가 될 때까지 대기 상태로 들어갑니다. 따라서 일반적으로 run () 스레드 메소드는 종료되지 않지만 Workqueue에서 Runnable 작업을 계속 실행하여 스레드 재사용 원칙을 달성합니다.
2. 최대 동시 수를 제어하십시오
그렇다면 언제 작업 큐에 넣을 수 있습니까? 노동자는 언제 창조됩니까? Worker Run () 메소드를 실행하기 위해 새 스레드를 열기 위해 start ()라는 작업자의 스레드는 언제입니까? 위의 분석에 따르면 작업자의 런 워크 ()는 연속적으로 작업을 연속적으로 수행하는 것으로 나타 났으므로 동시성 자체가 어떻게 나타 납니까?
실행할 때 위의 작업 중 일부를 수행 할 것이라고 생각하기 쉽습니다 (Runnable Runnable). 그것이 어떻게 실행되는지 봅시다.
실행하다:
단순화 된 코드
public void execute (runnable command) {if (command == null) 새 nullpointerexception (); int c = ctl.get (); // 현재 스레드 <corepoolsizeif (c) <corepoolsize) {// 새 스레드를 직접 시작합니다. if (addWorker (command, true)) return; c = ctl.get ();} // 활성 스레드 수> = corepoolsize // runstate가 실행 중입니다. if (! isrunning && remove (명령)) Reject (명령); // 스레드 풀에서 지정된 전략을 사용하여 작업을 거부합니다. // 1. 비 운반 상태는 새로운 작업을 거부합니다. // 2.추가 작업자 :
단순화 된 코드
개인 부울 추가 작업자 (runnable firsttask, boolean core) {int wc = workercountof (c); if (wc> = (core? corepoolsize : maximumpoolsize)) {return false;} w = 새 작업자 (FirstTask); 최종 스레드 t = w.thread; t.start ();}.코드에 따르면 스레드 풀 작업 중에 작업을 추가하는 위에서 언급 한 상황을 살펴 보겠습니다.
* 실행중인 스레드 수가 CorePoolSize보다 작은 경우이 작업을 즉시 실행할 스레드를 작성하십시오.
* 실행중인 스레드 수가 CorePoolSize보다 크거나 동일하면이 작업을 대기열에 넣으십시오.
* 현재 큐가 가득 차 있고 실행중인 스레드 수가 MaximumpOollsize보다 적은 경우, 작업을 즉시 실행하려면 비 코어 스레드를 만들어야합니다.
* 대기열이 가득 차 있고 실행 된 스레드 수가 MaximumpOollsize보다 크거나 동일하면 스레드 풀에 예외가 RejectecutionException이 발생합니다.
그렇기 때문에 Android의 Asynctask가 병렬로 실행되고 최대 작업 수를 초과하고 RejectexecutionException을 던지는 이유입니다. 자세한 내용은 Asynctask 소스 코드 해석의 최신 버전과 Asynctask의 어두운면을 참조하십시오.
AddWorker를 통해 새 스레드가 성공적으로 생성되면 START () 및 FirstTask 를이 작업자의 run ()에서 실행 된 첫 번째 작업으로 사용하십시오.
각 작업자의 작업은 직렬 처리이지만 여러 근로자가 만들어지면 작업장을 공유하기 때문에 병렬로 처리됩니다.
따라서 최대 동시성 수는 CorePoolSize 및 MaximumpOllsize에 따라 제어됩니다. 일반 프로세스는 아래 그림으로 표시 될 수 있습니다.
위의 설명과 그림은 잘 이해 될 수 있습니다.
안드로이드 개발에 참여하고 핸들러의 원칙에 익숙하다면이 사진이 매우 친숙하다고 생각할 수 있습니다. 일부 프로세스는 핸들러, 루퍼 및 메아지에서 사용하는 프로세스와 매우 유사합니다. handler.send (메시지)는 실행 (runnuble)과 동일합니다. 루퍼로 유지되는 Meaasge 대기열은 Blockingqueue와 같습니다. 그러나 동기화 로이 큐를 유지해야합니다. 루프 루프의 루프 () 함수는 Meaasge 대기열에서 Meaasge를 가져오고 작업자의 런 워크 ()가 Blockingqueue에서 지속적으로 실행할 수 있습니다.
3. 스레드 관리
스레드 풀을 통해 스레드 재사용을 관리하고 동시성 번호를 제어하며 프로세스를 파괴 할 수 있습니다. 스레드 재사용 및 제어 동시성은 위에서 논의되었으며 스레드 관리 프로세스가 산재되어있어 이해하기 쉽습니다.
ThreadPooleExecutor에는 CTL atomicinteger 변수가 있습니다. 이 변수를 통해 두 내용이 저장됩니다.
모든 스레드의 수. 각 스레드는 더 낮은 29 비트의 스레드가 저장되고 3 비트의 Runstate가 저장되는 상태에 있습니다. 비트 작업을 통해 다른 값을 얻습니다.
개인 최종 AtomicInteger CTL = New AtomicInteger (CTLOF (running, 0)); // 스레드의 상태 상태 정적 int runstateof (int c) {int c) {return c & ~ 용량;} // 직원의 수를 얻습니다. 용량;} // 스레드가 실행 중인지 여부에 관계여기서 스레드 풀 셧다운 프로세스는 주로 Shutdown 및 ShutdownNow ()에 의해 분석됩니다. 우선, 스레드 풀에는 작업 추가 및 실행을 제어하기위한 5 개의 상태가 있습니다. 다음 세 가지 주요 유형이 소개됩니다.
실행 상태 : 스레드 풀이 정상적으로 실행 중이며 대기열에서 새로운 작업 및 프로세스 작업을 수락 할 수 있습니다.
종료 상태 : 새로운 작업은 허용되지 않지만 대기열의 작업이 실행됩니다.
정지 상태 : 더 이상 새로운 작업이 수락되지 않으며 대기열에서 작업 종료가 처리되지 않습니다. 이 방법은 Runstate가 종료되도록 설정하고 모든 유휴 스레드가 종료되며 여전히 작동하는 스레드는 영향을받지 않으므로 대기열의 작업 담당자가 실행됩니다.
ShutdownNow 메소드는 Runstate가 중지되도록 설정합니다. 종료 방법의 차이,이 방법은 모든 스레드가 종료되므로 대기열의 작업이 실행되지 않습니다.
요약 : ThreadPooleExecutor 소스 코드의 분석을 통해 스레드 풀 생성, 작업 추가 및 스레드 풀 실행 프로세스에 대한 일반적인 이해가 있습니다. 이러한 프로세스에 익숙하다면 스레드 풀을 사용하는 것이 더 쉽습니다.
그것으로부터 배운 동시성 제어의 일부와 생산자 소비자 모델의 작업 처리를 사용하는 것은 미래의 다른 관련 문제를 이해하거나 해결하는 데 큰 도움이 될 것입니다. 예를 들어, Android의 핸들러 메커니즘과 루퍼의 Messager 대기열도 Blookqueue를 사용하여 처리해도 괜찮습니다. 이것은 소스 코드를 읽는 게이비입니다.
위는 Java 스레드 풀을 정렬하는 정보입니다. 우리는 향후 관련 정보를 계속 추가 할 것입니다. 이 웹 사이트를 지원 해주셔서 감사합니다!