并发-自定义线程池

线程池

产生背景

  • 线程是一种系统资源,每创建一个新的线程,都要占用一定的内存,如果是高并发的情况下,如果为每个任务都创建一个新的线程,对内存的占用是相当大的,甚至可能出现OOM

  • 线程也不是创建的越多越好,从CPU的角度,CPU的时间片分配不过来,会让一部分线程进入阻塞,引起上下文切换,上下文切换越频繁,会导致系统性能降低

所以出现了线程池,创建一些线程让他们可以重复利用

自定义线程池在这里插入图片描述

Thread Pool:放置这些可以重用的线程

Blocking Queue:阻塞队列,体现生产者-消费者模式下,平衡它们之间速度差异

线程池中的线程相当于任务的消费者,main线程相当于任务的生产者

ThreadPoolExecutor

在这里插入图片描述
ThreadPoolExceutor使用int的高3位来表示线程池状态,低29位表示线程数量

状态名高3位接收新任务处理阻塞队列任务说明
RUNNING111YY
SHUTDOWN000NY不会接收新任务,但会处理阻塞队列剩余任务,比较温和的停止
STOP001NN会中断正在执行的任务,并抛弃阻塞队列任务,暴力停止
TIDYING010--任务全执行完毕,活动线程为0即将进入终结
TERMINATED011--终结状态

从数字上比较,TERMINATED>TIDYING>STOP>SHUTDOWN>RUNNING,注意:高三位,running符号位为1是负数

为什么不用两个整数来存储这两个信息?为了保证状态信息和线程数量对它们进行赋值操作时的原子性

这些信息存储在一个原子变量ctl中,这样就可以用一次cas原子操作进行赋值

构造方法
public ThreadPoolExecutor(int corePoolSize
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue
                          ThreadFactory threadFactory
                          RejectedExecutionHandler handler)
  • corePoolSize 核心线程数目(最多保存的线程数)
  • maximumPoolSize 最大线程数目 核心线程+救急线程(前提是配合有界队列)
  • keepAliveTime 生存时间-针对救急线程(任务来的太多,阻塞队列放不下了,救急线程会被创建来救急,有生存时间)
  • unit 时间单位-针对救急线程
  • workQueue 阻塞队列
  • threadFactory 线程工厂-可以为线程创建时取名
  • handler 拒绝策略(阻塞队列满了,救急线程也不够了才会执行)
    在这里插入图片描述
  • jdk提供了4种实现
    • AbortPolicy 让调用者抛出RejectedExecutionException异常,这是默认策略
    • CallerRunsPolicy 让调用者运行任务
    • DiscardPolicy 放弃本次任务
    • DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
  • 著名框架实现
    • Dubbo的实现,在抛出RejectedExceutionException异常之前会记录日志,并dump线程栈信息,方便定位问题
    • Netty的实现,是创建一个新线程来执行任务
    • ActiveMQ的实现,带超时等待(60s)尝试放入队列
    • PinPoint的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略

根据这个构造方法,jdk提供了众多工厂方法来创建线程池

newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads){
    return new ThreadPoolExecutor(nThreads,nThreads,
                                  0L,TimeUnit.MILLISECONDS,
                                  new LinkedBlockedQueue<Runnable>());
}
// 使用方式
ExecutorService pool = Executors.newFixedThreadPool(2);

特点

  • 核心线程数==最大线程数(没有救急线程),所以不需要超时时间
  • 阻塞队列是无界的,可以放任意数量的任务
  • 适用于任务量已知,相对耗时的任务
newCachedThreadPool
public static ExecutorService newCachedThreadPool(){
    return new ThreadPoolExecutor(0,Integer.MAX_VALUE,
                                  60L,TimeUnit.SECONDS,
                                  new SynchronounsQueue<Runnable>());
}

特点

  • 核心线程数是0,最大线程数是Integer.MAX_VALUE,救急线程的空闲生存时间是60s
    • 全部都是救急线程(60s后可以回收)
    • 救急线程可以无限创建
  • 队列采用了同步队列SynchronounsQueue,它没有容量,没有线程来取的时候线程放任务是放不进去的,会阻塞(一手交钱一手交货)
  • 整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲1分钟后释放线程
  • 适合任务数比较密集,但每个任务执行时间较短的情况
newSingleThreadExecutor
public static ExecutorService newSingleThreadPool(){
    return new FinalizableDelegateExecutorService
        (new ThreadPoolExecutor(1,1
                               0L,TimeUnit.MILLSECONDS,
                               new LinkedBlockedQueue<Runnable>()));
}

使用场景

希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程 也不会被释放

区别

  • 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作
  • Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
    • FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
  • Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
    • 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涛堆堆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值