ThreadPoolExecutor

1、线程池状态

(1)ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量

状态名 高 3 位 接收新任务 处理阻塞队列任务 说明
RUNNING 111 Y Y  
SHUTDOWN 000 N Y 不会接收新任务,但会处理阻塞队列剩余任务
STOP 001 N N 会中断正在执行的任务,并抛弃阻塞队列任务
TIDYING 010 - - 任务全执行完毕,活动线程为 0,即将进入终结 
TERMINATED 011 - - 终结状态

(2)若使用两个 int,分别存储线程池状态、线程数量,有两个原子变量,需要进行两次 CAS 原子操作 

(3)使用一个 int 存储信息,在一个原子变量 ctl 中,将线程池状态、线程个数合二为一,可以只用一次 CAS 原子操作进行赋值

2、构造方法

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

(1)corePoolSize:核心线程数目(最多保留的线程数)

(2)maximumPoolSize:最大线程数目

(3)keepAliveTime:救急线程的生存时间

(4)unit:救急线程的生存时间单位

(5)workQueue:阻塞队列

(6)threadFactory:线程工厂(创建线程时命名)

(7)handler:拒绝策略 

3、线程

(1)核心线程:只要创建,则一直存在

(2)救急线程:有存活时间

(3)核心线程数 + 救急线程数 = 最大线程数

(4)核心线程、救急线程都是懒加载

5、工作方式

(1)线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务

(2)当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入 workQueue 队列排队,直到有空闲的线程

(3)如果队列选择有界队列,那么任务超过队列大小时,会创建 maximumPoolSize - corePoolSize 数目的救急线程

(4)如果线程到达 maximumPoolSize,仍然有新任务这时,会执行拒绝策略

(5)当高峰过去后,超过 corePoolSize 的救急线程,如果一段时间没有任务做,需要结束节省资源,这个时间由 keepAliveTime 和 unit 来控制

6、JDK 提供 4 种拒绝策略实现

(1)AbortPolicy:让调用者抛出 RejectedExecutionException 异常,默认策略

(2)CallerRunsPolicy:让调用者运行任务

(3)DiscardPolicy:放弃本次任务

(4)DiscardOldestPolicy:放弃队列中最早的任务,本任务取而代之

7、其它框架提供拒绝策略

(1)Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方便定位问题

(2)Netty 的实现,是创建一个新线程来执行任务

(3)ActiveMQ 的实现,带超时等待(60s)尝试放入队列

(4)PinPoint 的实现,使用一个拒绝策略链,会逐一尝试策略链中每种拒绝策略

 

线程工厂

1、根据构造方法,JDK Executors 类中提供众多工厂方法,来创建各种用途的线程池

2、线程工厂的默认实现

Executors.defaultThreadFactory()
static class DefaultThreadFactory implements ThreadFactory {
    //poolNumber从1开始
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    //threadNumber从1开始
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
        Thread.currentThread().getThreadGroup();
        //线程名前缀
        namePrefix = "pool-" +
            poolNumber.getAndIncrement() +
            "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            //默认为非守护线程
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

3、newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

(1)固定大小线程池

(2)核心线程数 == 最大线程数

(3)不创建救急线程,因此也无需超时时间

(4)阻塞队列无界,可以放任意数量的任务

(5)应用场景:任务量已知,相对耗时的任务

4、newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

(1)带缓冲线程池

(2)核心线程数是 0,最大线程数是 Integer.MAX_VALUE,表示全部都是救急线程

(3)救急线程的空闲生存时间是 60s

(4)救急线程可以无限创建

(5)队列采用 SynchronousQueue,没有容量,阻塞队列(BlockingQueue 的一个实现),发送或消费线程会阻塞,只有一对消费和发送线程匹配上,才同时退出

5、newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

(1)单线程线程池

(2)应用场景:希望多个任务排队执行(串行)

(3)线程数固定为 1,任务数多于 1 时,会放入无界队列排队

(4)任务执行完毕,这唯一的线程也不会被释放

6、newScheduleThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}

(1)线程池支持定时以及周期性执行任务

(2)创建一个 corePoolSize 为传入参数,最大线程数为整形的最大数的线程池

(3)线程池中具有指定数量的线程,即便是空线程也将保留

(4)可定时或延迟执行线程活动

(5)应用场景:多个后台线程执行周期任务

7、newWorkStealingPool

public static ExecutorService newWorkStealingPool(int parallelism) {
   /**
    * parallelism:并行级别,通常默认为 JVM 可用的处理器个数
    * factory:用于创建 ForkJoinPool 中使用的线程。
    * handler:用于处理工作线程未处理的异常,默认为 null
    * asyncMode:用于控制 WorkQueue 的工作模式:队列---反队列
    */
    return new ForkJoinPool(parallelism,
                            ForkJoinPool.defaultForkJoinWorkerThreadFactory,
                            null,
                            true);
}

(1)JDK 1.8 提供的线程池,底层使用 ForkJoinPool 实现

(2)创建一个拥有多个任务队列的线程池,可以减少连接数

(3)创建当前可用 CPU 核数的线程,并行执行任务

(4)应用场景:耗时多,可并行执行的任务

8、区别

(1)自定义创建一个单线程串行执行任务:如果任务执行失败而终止,那么没有任何补救措施,而线程池还会新建一个线程,保证线程池正常工作

(2)Executors.newSingleThreadExecutor():线程个数始终为 1,不能修改,FinalizableDelegatedExecutorService 应用装饰器模式,只对外暴露 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法

(3)Executors.newFixedThreadPool(1):初始时为1,以后还可以修改,对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改

9、提交任务

(1)在将来某个时候执行给定的任务,任务可以在新线程或现有的合并的线程中执行,如果任务无法提交执行,由于此执行程序已关闭或已达到其容量,该任务将由当前的 RejectedExecutionHandler 处理

public void execute(Runnable command)

(2)提交一个 Runnable 任务以执行,并返回一个代表该任务的 Future。该 Future 的 get 方法在成功完成后,将返回 null

Future<?> submit(Runnable task)

(3)提交一个 Runnable 任务以执行,并返回一个代表该任务的 Future,该 Future 的 get 方法将在成功完成后,返回给定的结果

<T> Future<T> submit(Runnable task, T result)

(4)提交一个返回值的任务以执行,并返回一个代表该任务的待定结果的 Future。Future 的 get 方法将在成功完成后,返回任务的结果

<T> Future<T> submit(Callable<T> task);

(5)执行给定的任务,当所有任务完成后,返回一个持有其状态和结果的 Future 链表。对于返回链表中的每个元素,Future.isDone 都为 true

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;

(6)执行给定的任务,当所有任务完成或超时结束时(以先发生者为准),返回一个持有其状态和结果的 Future 链表。对于返回的链表中的每个元素,Future.isDone 为 true。返回时,未完成的任务被取消

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;

(7)执行给定的任务,如果有任务成功完成(即没有抛出异常),则返回其中的结果。在正常或异常返回时,未完成的任务被取消

<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;

(8)执行给定的任务,如果有任务在给定的超时时间内成功完成(即没有抛出异常),则返回其结果。在正常或异常返回时,未完成的任务被取消

<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

10、关闭线程池

(1)线程池状态变为 SHUTDOWN,不会接收新任务,但已提交任务会执行完,此方法不会阻塞调用线程的执行。如果已经关闭,调用没有额外的效果

public void shutdown()

(2)线程池状态变为 STOP,不会接收新任务,会将队列中的任务返回,并用 interrupt 中断正在执行的任务

public List<Runnable> shutdownNow()

(3)不在 RUNNING 状态的线程池,此方法就返回 true

public boolean isShutdown()

(4)如果线程池在 shutdown() 或 shutdownNow() 后,处于终止过程中,但还没有完全终止,则返回 true

public boolean isTerminating()

(5)如果所有的任务都在关闭后完成,线程池状态为 TERMINATED,则返回 true

public boolean isTerminated()

(6)堵塞调用线程,直到所有任务在 shutdown 请求后完成执行,或超时发生,或当前线程被中断,以先发生者为准

public boolean awaitTermination

 

异步模式:工作线程

1、定义

(1)让有限的工作线程(Worker Thread),轮流异步处理无限多的任务

(2)可以将其归类为分工模式

(3)典型实现:线程池,体现享元模式 

2、饥饿

(1)固定大小线程池会有饥饿现象

(2)解决:不同的任务类型,采用不同的线程池

3、创建合适数量的线程池

(1)数量过小:导致程序不能充分地利用系统资源、容易导致饥饿

(2)数量过大:导致更多的线程上下文切换,占用更多内存

4、CPU 密集型运算

(1)CPU 核心数 + 1:实现最优 CPU 利用率

(2)+1:保证当线程由于页缺失故障(操作系统)或其它原因导致暂停时,有额外线程备用,保证 CPU 时钟周期不被浪费

5、I/O 密集型运算

(1)CPU 不总是处于繁忙状态,可以利用多线程提高它的利用率

(2)经验公式:线程数 = 核数 * 期望 CPU 利用率 * (CPU 计算时间 + 等待时间) / CPU 计算时间

 

任务调度线程池

1、ScheduledThreadPoolExecutor

2、任务调度线程池加入之前,可以使用 java.util.Timer 来实现定时功能

(1)优点:简单易用

(2)缺点:由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务

3、延时执行

(1)创建并执行一个一次性动作,在给定的延迟后开始启用

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)

(2)创建并执行一个 ScheduledFuture,在给定的延迟后启用

public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)

4、定时执行:周期性任务

(1)创建并执行一个周期性动作,该动作首先在给定的初始延迟后启用,然后在给定的周期后启用。如果任务的任何执行遇到了异常,后续的执行将被抑制。否则,该任务只能通过取消或终止执行器来终止

(2)若任务执行时间 > 时间间隔,则开始任务之间没有间隔,但执行不会重叠

(3)任务开始的同时,开始计算周期时间,即周期时间在任务执行过程中被消耗

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

(4)创建并执行一个周期性动作,在给定的初始延迟后首先启用,随后在一个执行的终止和下一个执行的开始之间有给定的延迟。如果任务的任何执行遇到了异常,后续的执行就会被抑制。否则,该任务将只通过取消或终止执行者来终止

(5)任务结束时,才开始计算周期时间

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

 

正确处理执行任务异常

1、try-catch,主动捕捉、处理异常

2、使用 Future

 

在 ThreadPoolExecutor 构造方法中提供阻塞队列

1、工作队列(workQueue):提交未执行的任务队列

2、BlockingQueue 接口的对象,仅用于存储 Runnable 任务

3、直接提交队列

(1)由 SynchronousQueue 对象提供

(2)该队列没有容量,提交给线程池的任务不会被真实的保存,总是将新的任务提交给线程执行

(3)如果没有空闲线程,则尝试创建新的线程

(4)如果线程数量已经达到 maxinumPoolSize 规定的最大值,则执行拒绝策略

4、有界任务队列

(1)由 ArrayBlockingQueue 实现

(2)在创建 ArrayBlockingQueue 对象时,可以指定一个容量

(3)如果线程池中线程数小于 corePoolSize 核心线程数,则创建新的线程

(4)如果大于 corePoolSize 核心线程数,则加入等待队列

(5)如果队列已满,则无法加入

(6)在线程数小于 maxinumPoolSize 指定的最大线程数前提下,会创建新的线程来执行

(7)如果线程数大于 maxinumPoolSize 最大线程数,则执行拒绝策略

5、无界任务队列

(1)由 LinkedBlockingQueue 对象实现

(2)与有界队列相比,除非系统资源耗尽,否则无界队列不存在任务入队失败的情况

(3)在系统线程数小于 corePoolSize 核心线程数,则创建新的线程来执行任务

(4)当线程池中线程数量大于 corePoolSize 核心线程数,则把任务加入阻塞队列

6、优先任务队列

(1)通过 PriorityBlockingQueue 实现

(2)带有任务优先级的队列,一个特殊的无界队列

(3)ArrayBlockingQueue、LinkedBlockingQueue 队列都是按照先进先出算法处理任务,PriorityBlockingQueue 队列中可以根据任务优先级顺序先后执行

 

扩展线程池

1、ThreadPoolExecutor 线程池提供两个方法

(1)在线程池执行某个任务前,会调用 beforeExecute()

protected void beforeExecute(Thread t, Runnable r)

(2)在任务结束后(任务异常退出),会执行 afterExecute()

protected void afterExecute(Runnable r, Throwable t)

2、ThreadPoolExecutor 类中定义一个内部类 Worker

(1)ThreadPoolExecutor 线程池中的工作线程就是 Worker 类的实例

(2)Worker 实例在执行时,会调用 beforeExecute()、afterExecute()

 

Tomcat 线程池

1、LimitLatch:限流,控制最大连接个数,类似 J.U.C 中的 Semaphore

2、Acceptor:只负责接收新的 socket 连接

3、Poller:只负责监听 socket channel 是否有可读的 I/O 事件,一旦可读,封装一个任务对象(socketProcessor),提交给 Executor 线程池处理

4、Executor 线程池中的工作线程最终负责处理请求

5、Tomcat 线程池扩展 ThreadPoolExecutor

(1)如果总线程数达到 maximumPoolSize,不会立刻抛 RejectedExecutionException 异常

(2)而是再次尝试将任务放入队列,如果仍失败,才抛出 RejectedExecutionException 异常

6、Connector 配置

配置项 默认值 说明
acceptorThreadCount 1 acceptor 线程数量
pollerThreadCount 1 poller 线程数量
minSpareThreads 10 核心线程数,即 corePoolSize
maxThreads 200 最大线程数,即 maximumPoolSize
executor - Executor 名称,用来引用下面的 Executor

7、Executor 线程配置

配置项 默认值 说明
threadPriority 5 线程优先级
daemon true 是否守护线程
minSpareThreads 25 核心线程数,即 corePoolSize
maxThreads 200 最大线程数,即 maximumPoolSize
maxIdleTime 60000 线程生存时间,单位是毫秒,默认值即 1 分钟
maxQueueSize Integer.MAX_VALUE  队列长度,可以视作无界队列,但存在救急线程
prestartminSpareThreads false 核心线程是否在服务器启动时启动 

8、创建线程流程

posted @ 2022-09-23 22:00  半条咸鱼  阅读(58)  评论(0)    收藏  举报