자세한 스레드 풀 ThreadPoolExecutor입니다

면책 조항 :이 문서는 블로거 원본입니다, 추적 에 의해-SA의 CC 4.0 저작권 계약, 복제, 원본 소스 링크이 문을 첨부 해주세요.
이 링크 : https://blog.csdn.net/ThinkWon/article/details/102541900

왜 스레드 풀을 사용

제대로 관리되지 스레드가 쉽게 시스템 문제를 일으킬 수있는 경우에 실제 사용에서, 스레드는 시스템 리소스를 점유한다. 따라서, 사용하는 대부분의 동시 프레임 워크에서 스레드 풀을 스레드를 관리하는 스레드 풀 관리 스레드는 주로 다음과 같은 장점이 있습니다 :

  1. 자원 소비를 줄입니다 . 다중화 스레드와 스레드 기존 최대한 시스템 성능 손실을 감소시키는 폐쇄의 수를 감소;
  2. 시스템 응답 성을 향상 . 스레드를 생성하는 프로세스를 제거하고 따라서 시스템의 전체 응답 속도를 향상 스레드 재사용;
  3. 관리 성을 향상 스레드 . 무제한 생성이뿐만 아니라 시스템 리소스를 소모뿐만 아니라 스레드를 관리하는 스레드 풀을 사용할 필요가 있으므로, 시스템의 안정성을 줄일 경우 스레드는, 희소 한 자원이다.

자세한 스레드 풀

스레드 풀 만들기

스레드 풀은 주로 만들고 있는 ThreadPoolExecutor 많은 ThreadPoolExecutor에 오버로드 된 생성자는 생성자에 의해 대부분의 매개 변수는 매개 변수가 스레드 풀을 만들기 위해 구성해야하는 이해하기가 완료하는 클래스입니다. ThreadPoolExecutor입니다 생성자 방법 :

ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

다음 매개 변수는 설명한다 :

  1. corePoolSize를가 : 핵심 스레드 풀의 크기를 나타냅니다. 스레드 풀 스레드의 현재 수, 핵심 corePoolSize를 도달하지 않을 경우, 작업을 제출할 때 현재의 코어 스레드 풀 유휴 스레드 경우에도 제출 된 작업을 수행 할 수있는 새로운 스레드를 생성합니다. thread의 현재의 코어 수는 스레드 풀은 corePoolSize를 도달 한 경우, 스레드를 다시 만들 수 없습니다. 당신은 호출하는 경우 prestartCoreThread()또는 prestartAllCoreThreads()모든 코어 스레드가 생성되고 시작됩니다 때 스레드 풀이 만들어집니다.
  2. maximumPoolSize를이 : 스레드 풀 스레드의 최대 수를 만들 수 있음을 나타냅니다. 큐가 가득, 현재는 스레드 풀 스레드 maximumPoolSize를의 수를 초과하지 않는 경우 차단 된 경우, 작업을 수행 할 수있는 새로운 스레드를 만들 것입니다.
  3. 이 KeepAliveTime : 유휴 스레드의 생존 시간. 스레드 풀 스레드의 현재 수는 corePoolSize를 초과하고 있으며, 스레드가이 KeepAliveTime보다 더 많은 유휴 상태 인 경우, 가능한 한 많은 시스템 자원 소비를 줄일 수 있습니다 이러한 유휴 스레드를 파괴한다.
  4. 단위 : 시간의 단위. 지정된 시간 단위는이 KeepAliveTime입니다.
  5. 이 Workqueue는 : 큐를 차단. 블로킹 큐에서 작업을 저장하는 큐를 차단 이 문서를 볼 수 있습니다 . 당신은 사용할 수 있습니다 ArrayBlockingQueue를, LinkedBlockingQueue 등, SynchronousQueue는, 인 PriorityBlockingQueue .
  6. ThreadFactory를 : 팩토리 클래스의 스레드를 만듭니다. 당신은 문제의 원인을 찾기도 쉽게 동시성 문제가있는 경우, 스레드 이름에서 각 공장에 대해 생성되는 스레드를 지정하여보다 의미 설정할 수 있지만.
  7. 핸들러 : 포화 전략. 큐를 차단 스레드 풀이 가득하고, 지정된 thread가 현재의 thread 풀을 이미 포화되어 있음을 나타냅니다 열리면, 우리는 이러한 상황에 대처하기위한 전략을 채택 할 필요가있다. 사용 전략의 이러한 유형은 다음과 같습니다
    1. AbortPolicy : 직접 거부 작업 제출 및 던져 RejectedExecutionException에게 예외를;
    2. CallerRunsPolicy : 만있는 호출자의 스레드로 작업을 수행하는 단계;
    3. DiscardPolicy은 : 직접 삭제 작업을 처리하지 않는다;
    4. 의 DiscardOldestPolicy : 현재 작업의 블로킹 큐 저장소 긴 작업 실행을 폐기

실행 논리 스레드 풀

스레드 풀에 의해 생성 ThreadPoolExecutor입니다, 작업 실행 과정 무엇을 제출 한 후,의 소스 코드를 통해 살펴 보자. 다음 원본이 방법을 실행 :

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    int c = ctl.get();
	//如果线程池的线程个数少于corePoolSize则创建新线程执行当前任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
	//如果线程个数大于corePoolSize或者创建线程失败,则将任务存放在阻塞队列workQueue中
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
	//如果当前任务无法放进阻塞队列中,则创建新的线程来执行任务
    else if (!addWorker(command, false))
        reject(command);
}

메소드 실행 로직 댓글을 볼 실행 ThreadPoolExecutor입니다. ThreadPoolExecutor에의 실행 방법에 대하여 개략도 수행 :

여기에 그림 삽입 설명

방법을 실행 실행 논리 여러 상황이있다 :

  1. 스레드가 현재 corePoolSize를 이하를 실행하는 경우, 그것은 새로운 작업을 수행하기 위해 새로운 스레드를 생성합니다;
  2. 실행 스레드의 수와 같거나 corePoolSize를보다 큰 경우, 작업이이 Workqueue에 저장되어있는 블록 큐에 제출 될 것입니다;
  3. 현재이 Workqueue 대기열이 꽉 찬 경우,이 작업을 수행하기 위해 새로운 스레드를 생성합니다;
  4. 스레드 수는 maximumPoolSize를 초과 한 경우, 포화 전략 RejectedExecutionHandler를 처리하는 데 사용됩니다.

스레드 풀의 디자인이 사용하는 것입니다 주목해야한다 대기열이 Workqueue 및 maximumPoolSize를 스레드 풀 스레드의 최대 수 차단, 핵심 스레드 풀 corePoolSize를을 , 작업을 처리하기 위해이 캐싱 전략은, 사실, 이러한 디자인은 프레임 워크에 사용되는 필요를 .

스레드 풀을 닫습니다

스레드 풀을 닫고, 당신이 할 수있는 shutdownshutdownNow이러한 두 가지 방법. 그들의 원칙은 인터럽트 스레드 다음에 풀의 모든 스레드를 통해입니다. shutdown그리고 shutdownNow같은 곳이 없습니다 :

  1. shutdownNow首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行和未执行任务的线程,并返回等待执行任务的列表;
  2. shutdown只是将线程池的状态设置为SHUTDOWN状态,然后中断所有没有正在执行任务的线程

可以看出shutdown方法会将正在执行的任务继续执行完,而shutdownNow会直接中断正在执行的任务。调用了这两个方法的任意一个,isShutdown方法都会返回true,当所有的线程都关闭成功,才表示线程池成功关闭,这时调用isTerminated方法才会返回true。

线程池的工作原理

当一个并发任务提交给线程池,线程池分配线程去执行任务的过程如下图所示:

여기에 그림 삽입 설명

从图可以看出,线程池执行所提交的任务过程主要有这样几个阶段:

  1. 先判断线程池中核心线程池所有的线程是否都在执行任务。如果不是,则新创建一个线程执行刚提交的任务,否则,核心线程池中所有的线程都在执行任务,则进入第2步;
  2. 判断当前阻塞队列是否已满,如果未满,则将提交的任务放置在阻塞队列中;否则,则进入第3步;
  3. 判断线程池中所有的线程是否都在执行任务,如果没有,则创建一个新的线程来执行任务,否则,则交给饱和策略进行处理

线程池阻塞队列

作用:用来存储等待执行的任务

线程的公平访问队列:指阻塞的线程可以按照阻塞的先后顺序访问队列,即先阻塞先访问线程。为了保证公平性,通常会降低吞吐量

常见阻塞队列

ArrayBlockingQueue:一个用数组实现的有界阻塞队列,按照先入先出(FIFO)的原则对元素进行排序。
不保证线程公平访问队列,使用较少

PriorityBlockingQueue:支持优先级的无界阻塞队列,使用较少

LinkedBlockingQueue:一个用链表实现的有界阻塞队列,队列默认和最长长度为Integer.MAX_VALUE。
队列按照先入先出的原则对元素进行排序,使用较多

  • 吞吐量通常要高于 ArrayBlockingQueue
  • Executors.newFixedThreadPool() 使用了这个队列

SynchronousQueue:不储存元素(无容量)的阻塞队列,每个put操作必须等待一个take操作,
否则不能继续添加元素。支持公平访问队列,常用于生产者,消费者模型,吞吐量较高,使用较多

  • 每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态
  • 吞吐量通常要高于 LinkedBlockingQueue
  • Executors.newCachedThreadPool使用了这个队列

线程池的饱和策略

定义:当队列和线程池都满了,说明线程池处于饱和状态,必须采取一种策略处理新提交的任务。

常见策略

AbortPolicy:中断策略,直接抛出异常

CallerRunsPolicy:调用者运行策略,让调用者所在线程来运行策略

DiscardOldestPolicy:舍弃最旧任务策略,丢弃队列中最旧的任务,然后重试任务的提交执行( execute() )

DiscardPolicy:舍弃策略,不处理,直接丢弃

自定义策略

如何合理配置线程池参数?

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

  1. 任务的性质:CPU密集型任务,IO密集型任务和混合型任务。
  2. 任务的优先级:高,中和低。
  3. 任务的执行时间:长,中和短。
  4. 任务的依赖性:是否依赖其他系统资源,如数据库连接。

任务性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务配置尽可能少的线程数量,如配置CPU个数+1的线程数的线程池。IO密集型任务则由于需要等待IO操作,线程并不是一直在执行任务,则配置尽可能多的线程,如配置两倍CPU个数+1。混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。我们可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。

优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。

执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。

依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。

并且,阻塞队列最好是使用有界队列,如果采用无界队列的话,一旦任务积压在阻塞队列中的话就会占用过多的内存资源,甚至会使得系统崩溃。

当然具体合理线程池值大小,需要结合系统实际情况,在大量的尝试下比较才能得出,以上只是前人总结的规律。

最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:

스레드의 최적 개수 CPU의 = (나사 기다리는 쓰레드 CPU 시간 비율 + 1) * 번호

결론을 도출 할 수 :
스레드가 더 높은 비율을 기다린 더 필요 스레드입니다. CPU 시간 스레드의 비율이 높을수록, 적은 수의 스레드가 필요합니다.
기본적으로 공식 위의 스레드 전에 설정 CPU 및 IO 집약적 인 작업의 수입니다.

참조 "예술 자바 병행 프로그래밍."

추천

출처blog.csdn.net/ThinkWon/article/details/102541900