>  기사  >  시스템 튜토리얼  >  Java 스레드 풀 작동 방식

Java 스레드 풀 작동 방식

WBOY
WBOY앞으로
2024-01-12 08:18:05927검색
소개 우리 개발에서는 "풀"이라는 개념이 흔합니다. 데이터베이스 연결 풀, 스레드 풀, 개체 풀, 상수 풀 등이 있습니다. 아래에서는 스레드 풀의 베일을 단계별로 밝히기 위해 주로 스레드 풀에 중점을 둡니다.
스레드 풀 사용의 이점

1. 자원 소비를 줄입니다

생성된 스레드를 재사용하여 스레드 생성 및 소멸로 인한 소비를 줄일 수 있습니다.

2. 응답 속도 향상

작업이 도착하면 스레드가 생성될 때까지 기다리지 않고 즉시 작업을 실행할 수 있습니다.

3. 스레드 관리 효율성 향상

스레드는 희소한 리소스이므로 제한 없이 생성하면 시스템 리소스를 소모할 뿐만 아니라 시스템의 안정성도 저하됩니다. 스레드 풀을 사용하여 통합 할당, 튜닝 및 모니터링을 해보세요.

스레드 풀 작동 방식 먼저 스레드 풀에 제출된 새 작업을 스레드 풀이 어떻게 처리하는지 살펴보겠습니다

1. 스레드 풀은 코어 스레드 풀의 모든 스레드가 작업을 실행하는지 여부를 결정합니다. 그렇지 않은 경우 작업을 수행하기 위해 새 작업자 스레드가 생성됩니다. 코어 스레드 풀의 모든 스레드가 작업을 실행 중인 경우 두 번째 단계를 수행합니다.

2. 스레드 풀은 작업 대기열이 가득 찼는지 여부를 결정합니다. 작업 대기열이 가득 차지 않으면 새로 제출된 작업이 이 작업 대기열에 저장되고 대기됩니다. 작업 대기열이 가득 찬 경우 3단계로 진행하세요

3. 스레드 풀은 스레드 풀의 모든 스레드가 작동 상태인지 확인합니다. 그렇지 않은 경우 작업을 수행하기 위해 새 작업자 스레드가 생성됩니다. 가득 차면 포화 전략에 넘겨 이 작업을 처리하세요

스레드 풀 포화 전략 여기서는 스레드 풀의 포화 전략이 언급되어 있으므로 포화 전략을 간략하게 소개하겠습니다.

AbortPolicy

Java 스레드 풀의 기본 차단 전략입니다. 이 작업을 수행하지 않고 런타임 예외를 직접 발생시킵니다. ThreadPoolExecutor.execute에는 try catch가 필요합니다. 그렇지 않으면 프로그램이 직접 종료됩니다.

DiscardPolicy

직접 포기하면 태스크가 실행되지 않고 메소드가 비어있습니다

가장 오래된 정책 버리기

큐에서 선두의 작업을 취소하고 이 작업을 다시 실행하세요.

CallerRunsPolicy

실행을 호출하는 스레드에서 이 명령을 실행하면 진입이 차단됩니다

사용자 정의 거부 정책(가장 일반적으로 사용됨)

RejectedExecutionHandler를 구현하고 전략 패턴을 직접 정의하세요

스레드 풀의 워크플로우 다이어그램을 보여주기 위해 ThreadPoolExecutor를 예로 들어보겠습니다

Java 线程池是如何工作的

Java 线程池是如何工作的

1. 현재 실행 중인 스레드 수가 corePoolSize보다 적으면 작업을 수행하기 위해 새 스레드를 생성합니다(이 단계를 수행하려면 전역 잠금을 얻어야 합니다).

2. 실행 중인 스레드가 corePoolSize 이상인 경우 BlockingQueue에 작업을 추가합니다.

3. 작업을 BlockingQueue에 추가할 수 없는 경우(큐가 가득 찬 경우) 비corePool에 새 스레드를 생성하여 작업을 처리합니다(이 단계를 수행하려면 전역 잠금을 얻어야 합니다).

4. 새 스레드를 생성하면 현재 실행 중인 스레드가 maximumPoolSize를 초과하는 경우 작업이 거부되고 RejectedExecutionHandler.rejectedExecution() 메서드가 호출됩니다.

위 단계를 수행하는 ThreadPoolExecutor의 전반적인 설계 아이디어는 Execute() 메서드를 실행할 때 전역 잠금을 최대한 획득하지 않는 것입니다(이는 심각한 확장성 병목 현상이 발생함). ThreadPoolExecutor가 워밍업을 완료한 후(현재 실행 중인 스레드 수가 corePoolSize보다 크거나 같음) 거의 모든 Execution() 메서드 호출은 2단계를 실행하며 2단계에서는 전역 잠금을 획득할 필요가 없습니다.

핵심 메소드 소스코드 분석 스레드 풀 메소드 실행에 추가된 코어 메소드의 소스코드를 살펴보면 다음과 같습니다.

으아악

addWorker가 어떻게 구현되는지 계속 살펴보겠습니다.

으아악

AddWorker 뒤에는 runWorker가 있습니다. 처음 시작되면 초기화 중에 전달된 firstTask 작업을 실행한 다음 workQueue에서 작업을 가져와서 실행합니다. keepAliveTime

이기만 하면 으아악

getTask가 어떻게 실행되는지 볼까요

으아악

processWorkerExit가 어떻게 작동하는지 살펴보겠습니다

으아악

TryTurminate

processWorkerExit 메서드는 스레드 풀을 종료하기 위해 tryTerminate를 호출하려고 시도합니다. 이 메소드는 스레드 풀을 종료시킬 수 있는 조치(예: wakerCount 감소 또는 SHUTDOWN 상태의 큐에서 작업 제거 등) 후에 실행됩니다.

으아악

shutdown 메소드는 runState를 SHUTDOWN으로 설정하고 모든 유휴 스레드를 종료합니다. shutdownNow 메서드는 runState를 STOP으로 설정합니다. 종료 방법과의 차이점은 이 방법이 모든 스레드를 종료한다는 것입니다. 주요 차이점은 shutdown이 InterruptIdleWorkers 메서드를 호출하는 반면 shutdownNow는 실제로 Worker 클래스의 InterruptIfStarted 메서드를 호출한다는 것입니다.

구현은 다음과 같습니다:

으아악

스레드 풀 사용법 스레드 풀 생성 ThreadPoolExecutor를 통해 스레드 풀을 생성할 수 있습니다

    /**
     * @param corePoolSize 线程池基本大小,核心线程池大小,活动线程小于corePoolSize则直接创建,大于等于则先加到workQueue中,
     * 队列满了才创建新的线程。当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,
     * 等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads()方法,
     * 线程池会提前创建并启动所有基本线程。
     * @param maximumPoolSize 最大线程数,超过就reject;线程池允许创建的最大线程数。如果队列满了,
     * 并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务
     * @param keepAliveTime
     * 线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率
     * @param unit  线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、
     * 毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)
     * @param workQueue 工作队列,线程池中的工作线程都是从这个工作队列源源不断的获取任务进行执行
     */
    public ThreadPoolExecutor(int corePoolSize,
               int maximumPoolSize,
               long keepAliveTime,
               TimeUnit unit,
               BlockingQueue<Runnable> workQueue) {
        // threadFactory用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                Executors.defaultThreadFactory(), defaultHandler);
    }
向线程池提交任务

可以使用两个方法向线程池提交任务,分别为execute()和submit()方法。execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。通过以下代码可知execute()方法输入的任务是一个Runnable类的实例。

threadsPool.execute(new Runnable() {
        @Override
        public void run() {
        }
    });

submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

Future<Object> future = executor.submit(harReturnValuetask);
  try
    {
        Object s = future.get();
    }catch(
    InterruptedException e)
    {
        // 处理中断异常
    }catch(
    ExecutionException e)
    {
        // 处理无法执行任务异常
    }finally
    {
        // 关闭线程池
        executor.shutdown();
    }
关闭线程池

可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别,shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。

只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法。

合理的配置线程池

要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。

1、任务的性质:CPU密集型任务、IO密集型任务和混合型任务。

2、任务的优先级:高、中和低。

3、任务的执行时间:长、中和短。

4、任务的依赖性:是否依赖其他系统资源,如数据库连接。

性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先执行

如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先级队列,让执行时间短的任务先执行。依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,等待的时间越长,则CPU空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用CPU。

바운드 큐를 사용하는 것이 좋습니다. 제한된 대기열은 시스템의 안정성과 조기 경고 기능을 향상시킬 수 있으며 필요에 따라 수천 개와 같이 더 크게 설정할 수 있습니다. 때때로 우리 시스템의 백그라운드 작업 스레드 풀의 대기열과 스레드 풀이 가득 차서 버려진 작업에 대한 예외가 지속적으로 발생하는 경우가 있습니다. 조사 결과 데이터베이스에 문제가 있어 SQL 실행이 매우 느려지는 것으로 나타났습니다. 속도가 느립니다. 백그라운드 작업 스레드 풀에는 모든 작업을 쿼리하고 데이터베이스에 데이터를 삽입해야 하므로 스레드 풀에서 작업 중인 모든 스레드가 차단되고 작업이 스레드 풀에 백로그됩니다. 그 당시 무제한 대기열로 설정하면 스레드 풀에 점점 더 많은 대기열이 생겨 메모리가 가득 차서 백그라운드 작업뿐만 아니라 전체 시스템을 사용할 수 없게 될 수 있습니다. 물론 우리 시스템의 모든 작업은 별도의 서버에 배포되며 다양한 크기의 스레드 풀을 사용하여 다양한 유형의 작업을 완료하지만 이러한 문제가 발생하면 다른 작업에도 영향을 미칩니다.

스레드 풀 모니터링

시스템에서 스레드 풀을 광범위하게 사용하는 경우, 문제가 발생했을 때 스레드 풀의 사용량을 기반으로 문제를 빠르게 찾을 수 있도록 스레드 풀을 모니터링하는 것이 필요합니다. 스레드 풀에서 제공하는 매개변수를 통해 모니터링할 수 있으며, 스레드 풀 모니터링 시 다음 속성을 사용할 수 있습니다

  • taskCount: 스레드 풀이 실행해야 하는 작업 수입니다.
  • completedTaskCount: 작업 중에 스레드 풀이 완료한 작업 수로, taskCount보다 작거나 같습니다.
  • largestPoolSize: 스레드 풀에서 생성된 최대 스레드 수입니다. 이 데이터를 통해 스레드 풀이 가득 찼는지 여부를 알 수 있습니다. 이 값이 스레드 풀의 최대 크기와 같다면 스레드 풀이 가득 찼다는 의미입니다.
  • getPoolSize: 스레드 풀에 있는 스레드 수입니다. 스레드 풀이 소멸되지 않으면 스레드 풀에 있는 스레드가 자동으로 소멸되지 않으므로 크기가 증가할 뿐 줄어들지는 않습니다.
  • getActiveCount: 활성 스레드 수를 가져옵니다.

스레드 풀을 확장하여 모니터링합니다. 스레드 풀을 상속하고 스레드 풀의 beforeExecute, afterExecute 및 종료된 메서드를 다시 작성하여 스레드 풀을 사용자 정의하거나 스레드 풀이 닫히기 전, 후, 전에 모니터링하기 위해 일부 코드를 실행할 수 있습니다. 예를 들어 작업의 평균 실행 시간, 최대 실행 시간, 최소 실행 시간을 모니터링합니다.

위 내용은 Java 스레드 풀 작동 방식의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 linuxprobe.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제