java 多线程 【ThreadPoolExecutor】笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013066244/article/details/81303252

环境

java:1.7+

前言

个人笔记

排队策略

workQueue(阻塞队列) 没有满的情况下,排队策略才有效果。

如果阻塞队列满,执行的就是拒绝策略

  1. 不排队,直接提交
    将任务直接交给线程处理而不是放入队列,可使用SynchronousQueue
    如果不存在可用于立即运行任务的线程(即线程池中的线程都在工作),则试图把任务加入到缓存队列将会失败,
    因此会构造一个新的线程来处理新添加的任务,并将其加入到线程池中(corePoolSize-->maximumPoolSize扩容
    Executors.newCachedThreadPool()采用的便是这种策略。

  2. 无界队列
    可以使用LinkedBlockingQueue(基于链表的有界队列,FIFO),理论上是该队列可以对无限多的任务排队;
    将导致在所有corePoolSize线程都工作的情况下,将新任务加入到队列中。这样,创建的线程就不会超过corePoolSize,也因此,maximumPoolSize的值无效了。

  3. 有界队列
    可以使用ArrayBlockingQueue(基于数组结构的有界队列,FIFO),并指定队列的最大长度;
    使用有界队列可以防止资源耗尽,但也会造成超过队列大小和maximumPoolSize后,提交的任务被拒绝的问题,比较难调整和控制。

threadFactory

创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。

/**
 * The default thread factory
 */
static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    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;
    }
}

Executors静态工厂里默认的threadFactory,线程的命名规则是“pool-数字-thread-数字”;

RejectedExecutionHandler(饱和策略)

线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4中策略:

  1. AbortPolicy:直接抛出异常,默认策略
  2. CallerRunsPolicy:用调用者所在的线程执行任务
  3. DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务
  4. DiscardPolicy:直接丢弃任务

当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

ThreadPoolExecutor线程池执行流程

根据ThreadPoolExecutor源码前面大段的注释,我们可以看出,当试图通过excute方法将一个Runnable任务添加到线程池中时,按照如下顺序处理:

  1. 如果线程池中的线程数量少于corePoolSize,就创建新的线程来执行新添加的任务;
  2. 如果线程池中的线程数量大于等于corePoolSize,但队列workQueue未满,则将新添加的任务放到workQueue中,按照FIFO的原则依次等待执行(线程池中有线程空闲出来后依次将队列中的任务交付给空闲的线程执行);
  3. 如果线程池中的线程数量大于等于corePoolSize,且队列workQueue已满,但线程池中的线程数量小于maximumPoolSize,则会创建新的线程来处理被添加的任务;
  4. 如果线程池中的线程数量等于了maximumPoolSize,就用RejectedExecutionHandler来做拒绝处理

总结

当有新的任务要处理时,先看线程池中的线程数量是否大于corePoolSize,再看缓冲队列workQueue是否满,最后看线程数量是否大于maximumPoolSize
另外,当线程池中的线程数量大于corePoolSize时,如果里面有线程的空闲时间超过了keepAliveTime,就将其移除线程池。

Executors静态工厂创建几种常用线程池

Exectors工厂类提供了线程池的初始化接口,主要有如下几种:

newFixedThreadPool

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

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

创建一个指定工作线程数的线程池,其中参数 corePoolSizemaximumPoolSize 相等,阻塞队列基于LinkedBlockingQueue

它是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源。

newSingleThreadExecutor

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

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

初始化的线程池中只有一个线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行,内部使用LinkedBlockingQueue作为阻塞队列。

newCachedThreadPool

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

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

创建一个可缓存工作线程的线程池,默认存活时间60秒,线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列;

在没有任务执行时,当线程的空闲时间超过keepAliveTime,则工作线程将会终止,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销

newScheduledThreadPool

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

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

初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据

注意:
ScheduledExecutorService#scheduleAtFixedRate() 指的是“以固定的频率”执行,period(周期)指的是两次成功执行之间的时间

比如,scheduleAtFixedRate(command, 5, 2, second),第一次开始执行是5s后,假如执行耗时1s,那么下次开始执行是7s后,再下次开始执行是9s后

而ScheduledExecutorService#scheduleWithFixedDelay() 指的是“以固定的延时”执行,delay(延时)指的是一次执行终止和下一次执行开始之间的延迟

还是上例,scheduleWithFixedDelay(command, 5, 2, second),第一次开始执行是5s后,假如执行耗时1s,执行完成时间是6s后,那么下次开始执行是8s后,再下次开始执行是11s后

参考地址:

Java线程池ThreadPoolExecutor使用和分析(一)

猜你喜欢

转载自blog.csdn.net/u013066244/article/details/81303252