【多线程】线程池的创建和参数设定

为什么要使用线程池

在日常开发中为了提高代码运行效率,或多或少会用线程去执行异步任务,线程的创建和销毁是需要占用一定资源的。

首先我们看一下一个线程的创建步骤:

  • 为线程堆栈分配和初始化大量内存块
  • 需要进行系统调用,以便在主机OS中创建/注册本机线程
  • 描述符需要创建、初始化并添加到JVM内部数据结构中

池化技术的出现是为了重复利用已存在的线程,避免了频繁的创建和销毁。

线程池的初始化及参数

注意:线程池必须手动通过 ThreadPoolExecutor 的构造函数来声明,避免使用Executors 类的 newFixedThreadPool 和 newCachedThreadPool ,因为可能会有 OOM 的风险。

看下ThreadPoolExecutor中的构造方法

    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

corePoolSize 核心线程数

核心线程:要保留在池中的线​​程数,即使它们处于空闲状态,线程池创建的时候是空的,随着任务的调用,逐步增加到核心线程数的大小。当任务进来时,如果有核心线程空闲,则优先使用核心线程。

如何设计核心线程池的数量:

  • 如果是CPU密集型应用,则线程池大小设置为N+1 (N为CPU总核数)
  • 如果是IO密集型应用,则线程池大小设置为2N+1 (N为CPU总核数)
  • 线程等待时间(IO)所占比例越高,需要越多线程。
  • 线程CPU时间所占比例越高,需要越少线程。

1. 题外话:cpu密集型和IO密集型是什么

是任务、方法的类型

1. cpu密集型(计算密集型、cpu高了)

处理运算时间比较长,系统运行的大部分状况是CPU Loading 100%,不太需要访问I/O设备

1. 例如

计算圆周率、对视频进行高清解码

2. 要注意什么

尽量避免CPU的切换,任务同时进行的数量 = CPU的核心数

1. IO密集型

IO的速度远远低于CPU和内存的速度,cpu性能好,处理运算时间比较短,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作

1. 例如

Web应用

2. 要注意什么

可以充分利用CPU的资源,但不能开启任务数量太多,一般情况:任务同时进行的数量 = 2*CPU的核心数

maximumPoolSize 最大线程数

池中允许的最大线程数

keepAliveTime 非核心线程的活跃时间

当线程数大于核心数时,非核心线程如果过了keepAliveTime长时间还没有执行新的任务,则销毁线程。

unit 单位

是keepAliveTime的时间单位

workQueue 队列

用于在执行任务之前保存任务的队列。该队列将仅保存由 {@code execute} 方法提交的 {@code Runnable} 任务。

workQueue的类型为BlockingQueue,通常可以取下面三种类型:

队列的种类:

  • ArrayBlockingQueue 有界任务队列

基于数组的先进先出队列,此队列创建时必须指定大小;FIFO

  • LinkedBlockingQueue 无界任务队列

基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;FIFO

  • SynchronousQueue 同步队列

一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene

  • PriorityBlockingQueue 优先级队列

一个支持优先级的无界阻塞队列,可以通过其构造方法,自定义排序的规则,默认按照首字母从小到大排序,生产的时候随便,消费的时候会按照优先级消费

public PriorityBlockingQueue(int initialCapacity,
                                 Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.lock = new ReentrantLock();
        this.notEmpty = lock.newCondition();
        this.comparator = comparator;
        this.queue = new Object[initialCapacity];
    }

threadFactory 线程工厂

工厂类,创建新线程时使用的工厂

handler 饱和策略

由于达到线程边界和队列容量而阻塞执行时要使用的处理程序,默认是AbordPolicy,表示无法处理新任务,并抛出 RejectedExecutionException 异常

1. AbortPolicy(拒绝抛出异常)

    /**
     * A handler for rejected tasks that throws a
     * {@code RejectedExecutionException}.
     */
    public static class AbortPolicy implements RejectedExecutionHandler {
    
    }

丢弃任务并抛出RejectedExecutionException异常,默认策略

如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。

2. CallerRunsPolicy(让外面的线程去处理)

	/**
     * A handler for rejected tasks that runs the rejected task
     * directly in the calling thread of the {@code execute} method,
     * unless the executor has been shut down, in which case the task
     * is discarded.
     */
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        
    }

由调用线程(执行execute的线程)去处理该任务,如果调用线程关闭,则直接抛弃该任务

3. DiscardOldestPolicy(把最老的丢掉、喜新厌旧)

    /**
     * A handler for rejected tasks that discards the oldest unhandled
     * request and then retries {@code execute}, unless the executor
     * is shut down, in which case the task is discarded.
     */
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {

	}

丢弃队列最老的未执行的任务,然后重新提交被拒绝的这个任务(喜新厌旧的策略)

是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。

4. DiscardPolicy(偷偷的抛弃,不抛异常)

    /**
     * A handler for rejected tasks that silently discards the
     * rejected task.
     */
    public static class DiscardPolicy implements RejectedExecutionHandler {
	
	}

直接抛弃该任务,不会抛出异常。使用该策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。

1.例如

阅读量、点击量这些高频的但不需要很精确操作

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值