深入线程池

线程池

手写线程池:

https://github.com/yourkin666/my-ThreadPool/blob/main/TestPool.java#L45

ThreadPoolExecutor:

  • 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务
  • 当线程数达到corePoolSize 并没有线程空闲,这时再加入任务,新加入的任务会被加入workQueue阻塞队列排队,直到有线程空闲
  • 如果队列选择了有界队列,那么任务超过了队列大小时,会创建救急线程来处理任务
  • 如果线程达到了maximumPoolSize 仍然有新任务,这时会执行拒绝策略,拒绝策略有四种:
    • AbortPolicy 让调用者抛出RejectedExecutionException异常(默认策略
    • CallerRunsPolicy 让调用者运行任务
    • DiscardPolicy 放弃本次任务
    • DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
  • 当高峰过去后,超过corePoolSize的救急线程如果在一定时间内没事可做,会被回收
1.五种线程池状态:

ThreadPoolExecutor使用 int 的高3位来表示线程池状态,低29位表示线程数量

RUNNING(正常执行

SHUTDOWN(不会再接受新任务,但会把阻塞队列中的剩余任务完成

STOP(中断正在执行的任务,并抛弃阻塞队列中的任务

TIDYING(任务全部执行完毕,活动线程为0即将进入终结

TERMINATED(终结状态

线程池状态和线程数量信息存储在一个原子变量ctl中,便于一次cas原子操作进行赋值

2.构造方法:public ThreadPoolExecutor(…) {}

参数:

corePoolSize:核心线程数目

maximumPoolSize:最大线程数目(max - core = 救急线程数目

keepAliveTime:生存时间(针对救急线程

unit:时间单位(针对救急线程

workQueue:阻塞队列

threadFactory:线程工厂(创建线程时可以取名字

handler:拒绝策略

3.newFixedThreadPool:

适用于任务量已知,相对耗时的任务

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

特点:

  • 最大线程数 就是 核心线程数,没有救急线程

  • 阻塞队列是无界的

4.newCachedThreadPool:

整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲1min后释放线程

适合任务数比较密集,但每个任务执行时间较短的情况

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

特点:

  • 没有核心线程,全是救急线程
  • 队列是SynchronousQueue,它是没有容量的,没有线程来取,任务是放不进去的(一手交钱,一手交货
5.newSingleThreadExecutor:

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

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

这和创建单线程串行执行任务有什么区别:

  • 创建单线程串行执行,遇到执行失败就会终止线程,且没有任何补救措施;但线程池遇到这种情况,还会去新建线程,保证池的正常工作
  • Executors.newSingleThreadExecutor()线程始终是一个,不可被修改
    • FinalizableDelegatedExecutorService应用了装饰器模式,只对外暴露了ExecutorService接口,因此不能调用ThreadPoolExecutor中的特有方法
6.提交任务:
//执行任务
void execute(Runnable command);
//提交任务 task,用返回值future接收任务的执行结果
<T> Future<T> submit(Callable<T> task);
//提交tasks中所有任务
invokeAll
//提交tasks中所有任务,带超时时间
invokeAll
//提交tasks中所有任务,哪个任务先成功执行完毕,返回此任务的结果,其他任务取消
invokeAny    
//提交tasks中所有任务,哪个任务先成功执行完毕,返回此任务的结果,其他任务取消,带超时时间
invokeAny
7.关闭线程池:
void shutdown();//将线程池状态变为STOP
8.任务调度线程池:

可以延时和定时进行任务

9.Tomcat线程池:

Tomcat线程池拓展了ThreadPoolExecutor,行为稍有不同:

  • 如果总线程数达到maximumPoolSize
    • 这是不会立即抛RejectedExecutionException
    • 而是再次尝试将任务放入队列,如果还失败的话,才抛RejectedExecutionException

Fork/Join:

Fork/Join是JDK1.7加入的新的线程池实现(ForkJoinP,它体现的是一种分治思想,适用于能够进行任务拆分的cpu密集型运算(Fork/Join默认创建cpu核心数相同的线程池

所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解

Fork/Join在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运算效率

设计模式 - 工作线程:

有限的工作线程来轮流异步处理无限多的任务

不同的任务类型应该使用不同的线程池,这样能够避免饥饿,提升效率

像是服务员和厨师,要是都做请两个人把这两个任务一起做了,等同时来两个客人的时候,两人同时去服务客人,这时就没人做饭了,就是卡死在这一步,这就是一种饥饿

所以不同的任务类型应该使用不同的线程池

线程池的尺寸:

  • 过小,会导致程序不能充分利用系统资源,容易导致饥饿
  • 过大,会导致更多线程上下文切换,占用更多内存

CPU密集型运算:

​ 用CPU核数 + 1实现最优的CPU利用率

​ +1 是保证线程优于页缺失故障或其他原因导致暂停时,额外的线程 可以顶上去

I/O密集型运算:
CPU不总是繁忙状态

​ 线程数 = 核数 * 期望CPU利用率 * 总时间(CPU计算时间 + 等待时间)/ CPU计算时间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值