并发编程--线程池

一、为什么要使用线程池?

在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念

二、线程池参数

1、corePoolSize(核心线程数)

当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize,corePoolSize中的线程即使处于空闲状态,他们也不会被销毁

2、maximumPoolSize(最大线程数)

线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。

3、keepAliveTime(线程存活保持时间)

当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。

4.unit(存活时间单位)

空闲线程存活时间单位,keepAliveTime的计量单位

5、workQueue(任务队列):

用于传输和保存等待执行任务的阻塞队列。类型是BlockingQueue,详情可以看这篇博客

并发编程--堵塞队列_zhang09090606的博客-CSDN博客

一般情况下堵塞队列都使用LinkedBlockingQueue,因为无须指定长度,放入和取出元素使用不同的锁,双锁机制,互不影响,效率高,通用性强。

而ArrayBlockingQueue 必须指定长度,大了浪费内存,小了性能不高,使用同一把锁,效率低。

spring封装的线程池就是如果配置的队列大小不为使用LinkedBlockingQueue,如果为0使用SynchronousQueue,SynchronousQueue内部使用了大量CAS实现,性能很高

6、threadFactory(线程工厂):

用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。

7、handler(线程拒绝策略):

当线程池和队列都满了,再加入线程会执行此策略。

三、线程池流程

 四、监控线程池中异常

线程池的类implements AsyncConfigurer然后重写getAsyncUncaughtExceptionHandler方法

    /*异步任务中异常处理*/
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        LogFactory.build().withMarker("线程池异常", "异步任务时处理异常", "")
                .withMessage(String.format("异步任务执行失败")).error();
        return new SimpleAsyncUncaughtExceptionHandler();
    }

 五、自定义拒绝策略

 通过executor.setRejectedExecutionHandler(new NewAbortPolicy());设置拒绝策略

  private class NewAbortPolicy implements RejectedExecutionHandler {
        public NewAbortPolicy() {

        }
       //重写拒绝策略
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            LogFactory.build().withMarker("线程池异常", "超过最大限额", "")
                    .withMessage(String.format("线程数已超过最高配置:【%s】", "Task " + r.toString() + "rejected from" + executor.toString())).error();
            throw new RejectedExecutionException("Task " + r.toString() +
                    " rejected from " +
                    executor.toString());
        }
    }

六、核心线程数是否会被销毁?

当allowCoreThreadTimeOut手动设置为true或者执行的run方法抛出异常,核心线程都会被销毁,但是后者还是会创建新的线程称呼来,前者则销毁什么都不做,关键在于allowCoreThreadTimeOut为true则下面代码直接返回,不在执行addWorker方法

七、超过核心线程数的部分什么时候会被回收?

一般人都会回答如果经过 keepAliveTime 时间后,超过核心线程数的线程还没有接受到新的任务,就会被回收。对但是不完全准确,如下线程池

ExecutorService executorService = new ThreadPoolExecutor(2, 3, 30, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(2), 
                new DefaultThreadFactory("test"),
                new ThreadPoolExecutor.DiscardPolicy());

当前线程池的活跃线程是 3 个(2 个核心线程+ 1 个非核心线程),但是它们各自的任务都执行完成了。然后我每隔 3 秒往线程池里面扔一个耗时 1 秒的任务。那么 30 秒之后,活跃线程数是多少? 

按照正常思维应该是:核心线程是空闲的,每隔 3 秒扔一个耗时 1 秒的任务过来,所以仅需要一个核心线程就完全处理的过来。30 秒内,超过核心线程的那一个线程一直处于等待状态,所以 30 秒之后,就被回收了。但是实际情况是3个,并没有被回收

为什么会这样呢?

在上面的案例中,虽然线程都是空闲的,但是当任务来的时候不是随机调用的,而是轮询

由于是轮询,每三秒执行一次,所以非核心线程的空闲时间最多也就是 9 秒,不会超过 30 秒,所以一直不会被回收。基于这个 Demo,我们就从表象上回答了,为什么活跃线程数一直为 3。

 线程池的轮询机制

线程池其实是个生产者消费者模型,三个线程就是三个消费者,现在没有任务需要处理,它们就等着生产者生产任务,然后通知它们准备消费,而执行任务就是轮询队列中的线程去执行任务,

那么总共得有个队列去储存啊,实际上线程池就是使用AQS 的等待队列去实现的轮询

放入队列

所以,现在你知道当一个任务来了之后,这个任务该由线程池里面的哪个线程执行,这个不是随机的,也不是随便来的。是讲究一个顺序的。

什么顺序呢?

Condition 里面的等待队列里面的顺序。Conditionobject是AQS的内部类,具体在AQS文章细讲

所以最终总结下非核心线程怎么回收?

就是空闲时间达到设置的值就会回收,但是想要空闲需要一直没有轮询到它而不是优先使用核心线程去执行

那么触发回收时被回收的线程时核心线程还是非核心线程呢?

其实也是不一定的,因为在线程池里面,核心线程和非核心线程仅仅是一个概念而已,其实拿着一个线程,我们并不能知道它是核心线程还是非核心线程。

这个地方就是一个证明,因为当工作线程多余核心线程数之后,所有的线程都在 poll,也就是说所有的线程都有可能被回收:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值