JDK多线程基础(6):线程池(ThreadPoolExecutor)详解

线程池 ThreadPoolExecutor

Executors 工具本质

Executors 线程池工具类提供了很多创建不同特性的线程池,其内部核心都是创建 ThreadPoolExecutor对象,如

  1. 一般线程池: newFixedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}
  1. 定时线程池:newScheduledThreadPool()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
}

ThreadPoolExecutor 详解

构造方法详解
  1. int corePoolSize :线程池中的核心线程数量
  2. int maximumPoolSize : 线程池中最大的线程数量。corePoolSize 以上的线程创建必须满足两个条件:corePoolSizeworkQueue已满。
  3. long keepAliveTime : 当线程池中的线程数量,超过了核心线程数 corePoolSize ,多余的空闲线程的存活时间。即超过corePoolSize的空闲线程,在多长时间内销毁
  4. TimeUnit unit : keepAliveTime 的单位时间
  5. BlockingQueue<Runnable> workQueue : 任务队列,被提交但尚未执行的任务。本身是BlockingQueue,存放 Runnable
  6. ThreadFactory threadFactory : 线程工厂,用于创建线程。默认使用DefaultThreadFactory。建议使用自定义的ThreadFactory,可以自定义线程的名字,如使用 guavaThreadFactoryBuilder
  7. RejectedExecutionHandler handler : 拒绝策略,当任务太多来不及处理,如何拒绝
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
任务队列
  1. 直接提交的队列:SynchronousQueue
  • 没有容量,不保存任务,总是将任务提交给线程执行,如果没有空闲的线程,则创建线程,如达到最大数量,则执行拒绝策略
  • 通常需要设置很大的 maximumPoolSize 的值(Integer.MAX_VALUE),否则很容易执行异常;而且需要适当的延长 keepAliveTime 时间
  • 如果任务量多的话,会不断的创建线程,导致资源耗尽。
  1. 有界任务队列:ArrayBlockingQueue,需要设置容量参数,ArrayBlockingQueue(int capacity)
  • 有新任务需要执行,如果线程池实际线程数小于corePoolSize,优先创建新的线程
  • 若大于corePoolSize,任务进入等待队列
  • 等待队列已满,总线程数不大于maximumPoolSize的前提下,创建新的线程。
  • 若大于maximumPoolSize,执行拒绝策略
  • 可见 maximumPoolSize-corePoolSize 中的线程创建有一个必要条件为corePoolSize和任务队列已满。所有只有当系统非常繁忙,才有可能将线程数提升到corePoolSize以上,否则确保核心线程数维持在corePoolSize
  1. 无界任务队列:LinkedBlockingQueue
  • 无界任务队列不会有任务入队列失败的情况
  • 有新任务需要执行,如果线程池实际线程数小于corePoolSize,优先创建新的线程
  • 若已经达到corePoolSize,线程不会增加,任务进入等待队列
  • 如果任务的增加速度远远高于任务的处理速度,那么任务队列会越来越大,直到耗尽资源。即线程池的最大线程为corePoolSize,来不及处理则任务队列中等待。
  • 总结:不管任务数多少,线程池的最大线程只有corePoolSize个,来不及处理则任务进入无界队列等待,容易耗尽资源。
  1. 优先任务队列:PriorityBlockingQueue,控制任务的优先执行顺序,特殊的无界队列
  • ArrayBlockingQueueLinkedBlockingQueue 都是按照先进先出的算法处理
  • PriorityBlockingQueue 可以根据任务自身的优先级顺序先后执行
  1. 延迟队列:DelayedWorkQueueScheduledThreadPoolExecutor内部类,博客延迟线程池文章
  • ScheduledThreadPoolExecutorThreadPoolExecutor 子类
  • DelayedWorkQueue,是无界队列
拒绝策略
  1. JDK 内置的策略:所有的策略实现 RejectedExecutionHandler 接口
  • AbortPolicy : 直接抛出异常,默认的策略
  • DiscardOldestPolicy : 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务
  • CallerRunsPolicy : 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务
  • DiscardPolicy : 丢弃无法处理的任务,不予任何处理
  1. 自定义策略:实现 RejectedExecutionHandler 接口
  • Runnable r:请求执行的任务
  • ThreadPoolExecutor executor:当前的线程池
public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

不建议使用Executors的原因

  1. Executors 中的 newCachedThreadPool() 是使用直接提交的队列 SynchronousQueue,且maximumPoolSize 的值为Integer.MAX_VALUE,如果任务量多的话,会不断的创建线程,导致资源耗尽,不建议使用。
  2. Executors 中的 newSingleThreadExecutor()newFixedThreadPool() 都是使用无界任务队列 LinkedBlockingQueue,不管任务数有多少,任务队列永远不会满,导致线程池的最大线程只有 corePoolSize 个,来不及处理则任务进入无界队列等待,容易耗尽资源。
  3. Executors 中的newScheduledThreadPool() 使用延迟无界队列 DelayedWorkQueue,一样的道理,容易耗尽资源
  4. 如果任务量较小,且对于任务的把控有把握,Executors也可以简单使用。但是一般还是不建使用,建议自定义 ThreadPoolExecutor 实现线程池

自定义线程池实现

  1. 实现有优先级的任务线程
public class CustomTaskThread implements Runnable, Comparable<CustomTaskThread> {

    private String name;

    public CustomTaskThread() {
    }

    public CustomTaskThread(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(100);
            System.out.println(name + " ");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }


    @Override
    public int compareTo(CustomTaskThread o) {
        //线程名称中标注的优先级
        int me = Integer.parseInt(this.name.split("_")[1]);
        int other = Integer.parseInt(o.name.split("_")[1]);

        if (me > other) {
            return 1;
        } else if (me < other) {
            return -1;
        } else {
            return 0;
        }
    }
}

  1. 自定义线程池调用执行任务:使用优先级队列 PriorityBlockingQueue
/**
 * 建议使用带 ThreadFactory 的构造,方便自定义线程的名字
 * ThreadFactoryBuilder 是 guava 中的类
 */
private static void test2() {

    ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("custom-name-%d").build();
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(100, 200, 0, TimeUnit.SECONDS, new PriorityBlockingQueue<>(), threadFactory);
    for (int i = 0; i < 1000; i++) {
        threadPoolExecutor.execute(new CustomTaskThread("CustomThreadPoolExecutor_" + Integer.toString(999 - i)));
    }
    threadPoolExecutor.shutdown();
}
  1. 执行结果分析:一开始有大量的空闲线程,不需要等待队列而是被直接执行任务。之后就会进入优先级等待队列
CustomThreadPoolExecutor_999 
CustomThreadPoolExecutor_998 
CustomThreadPoolExecutor_997 
.
.
.
CustomThreadPoolExecutor_907 
CustomThreadPoolExecutor_900
CustomThreadPoolExecutor_0 
CustomThreadPoolExecutor_4 
CustomThreadPoolExecutor_1 
CustomThreadPoolExecutor_2 
CustomThreadPoolExecutor_3 
.
.
.

优化线程池大小

  1. 影响线程池大小的因素:CPU、内存等因素
  2. 《Java并发编程实践》中有个估算公式
Ncpu = CPU的数量
Ucpu = 目标CPU的使用率, 0<=Ucpu<=1
W/C = 等待时间与计算时间的比率

最优的线程池大小公式:
Nthread = Ncpu * Ucpu * (1 + W/C)
  1. Java 实现
  • CPU 数量: Runtime.getRuntime().availableProcessors()

扩展 ThreadPoolExecutor:

  1. 重载方法 beforeExecuteafterExecute
  2. 这两个方法在 ThreadPoolExecutor.Worker.runWorker()方法中被调用
final void runWorker(Worker w) {
    ...
    beforeExecute(wt, task);
    Throwable thrown = null;
    try {
        task.run();
    } catch (RuntimeException x) {
        thrown = x; throw x;
    } catch (Error x) {
        thrown = x; throw x;
    } catch (Throwable x) {
        thrown = x; throw new Error(x);
    } finally {
        afterExecute(task, thrown);
    }
    ...
}
  1. 自定义扩展ThreadPoolExecutor
public class CustomExpandThreadPoolExecutor extends ThreadPoolExecutor {

    public CustomExpandThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        System.out.println("线程执行开始前" + t.getId() + "_" + t.getName() + "_" + ((CustomTaskThread) r).getName());
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        System.out.println("线程执行结束后 " + Thread.currentThread().getName());
        System.out.println("结束后 PoolSize " + this.getPoolSize());
    }
}

经验

如何确定合适数量的线程

  • 计算型任务:cpu数量的1~2倍
  • IO型任务:需要多一些线程,具体根据IO阻塞时长进行考量决定。如Tomcat中默认的最大线程数为200(一般为cpu数量的30~40倍)

spring应用中线程池使用

参考spring相关博文

参考

  1. 源码地址

Fork me on Gitee

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值