线程池的设计与实现解析

线程池的设计与实现解析

  1.  概述
    1. 前言:

主要分享基于ThreadPoolExecutor实现的线程池设计和实现.

    1. 编写目的
  1. 介绍线程相关的基础知识;
  2. 介绍线程池的原理,及如何合理使用线程池;
    1. 适用人群
  1. 有Java基础,了解并发的开发人员;
  1.  基础知识介绍
    1.  锁

2.1.1 Synchronized

ART VM原理简述:

获取/释放锁的过程实际上,由monitor对象进行系统调用(monitorEnter/exit),从而进行获取和释放锁的操作;

 

Hot Spot VM Synchronized内部优化:

  1. 偏向锁(Biase Lock): 用于单线程的情况;
  2. 轻量级锁(Light weight Lock): 用于同一时间没有发生锁竞争的情况(前期通过自旋的方式获取锁);
  3. 重量级锁(Heavy weight Lock): 用于有发生锁竞争的情况;

 

Art VM Synchronized内部优化:

  1. 瘦锁(ThinLock): 开始的时候,会尝试通过自旋方式,进行锁的获取;如果尝试多次后,会进行锁膨胀(inflate)为FatLock;
  2. 胖锁(FatLock): 如果是非持有锁线程获取锁(通过Futex的方式)将线程挂起;

 

ART VM的Synchronized获取锁实现流程图:

 

用法:

①void Synchronized testLock1 () {}//方法级

②Void testLock2 () {//代码块

Synchronized(xxx.class) {//class对象为锁对象

//do something

}

}

③Void testLock2 () {//代码块:

Synchronized(obj) {//实例对象为锁对象

//do something

}

}

 

2.1.2 Lock

原理简述:

Java.util.concurrent并发包下基于Java语言实现的锁,其实现都是基于AQS(CAS+volatile+LockSupport).

 

分类:

  1. 乐观锁/悲观锁

乐观锁: 可以理解成锁很块就会被释放,并获取到,所以会做轮询的实现;

悲观锁: 可以理解成锁不能马上获取,所以会进行系统调用,进行线程挂起操作;

  1. 独占锁/共享锁/可重入锁

独占锁: 同一时间,只能一个线程获取锁;

共享锁: 同一时间,多个线程可获取锁, 如:ReentrantReadWriteLock.readlock;

可重入锁: 统一线程,可多次获取同一个锁, 如:ReentrantLock;

 

用法:

Lock.lock();

try{

//同步代码块

}finally{

Lock.unlock();

}

    1.  阻塞队列(BlockingQueue)

实现原理简述: 是基于lock+condition实现的阻塞队列;

2.2.1 类图

 

简单来说,阻塞队列本质上还是用于盛放元素的集合,只是通过组合的方式,引入了Lock和Condition,从而让队列具备了支持生成者-消费者模型的功能;

2.2.2 代码实现简析

阻塞队列的put/take代码示例:

 

 

    1.  Java线程模型

简述:在ART vm实现中,每个Java的Thread通过系统调用pthread_create创建线程;

    1.  生产者消费者模型简析

 

举个悲伤的栗子:

生产者: 快递员;

消费者: 收件人;

缓存区: 丰巢;

  1. 时间:双十一之后;
  2. 地点:金山园区;
  3. 人物:快递小哥和一群在双十一剁手的同学;
  4. 事件:快递小哥让一群有快递的同学,现在去园区领取快递;
  5. 快递领取方式:杯具的是,快递小哥为了让自己方便并且无误的发快递,因此在现场叫一个名字,然后过来一个人取一个快递;(你还不能走,因为走了就默认没人认领,明天继续让帮你过来以这样的方式去快递)
  6. 问题分析:因为快递小哥只有一个人,他只能一个个的喊名字,而快递却又很多,你很可能需要在现场等好久都没叫到你的名字,导致你浪费时间也不得不留在原地等待,晚上还有加班改bug;
  7. 效率改进:因为你要赶着回去接bug,所以你建议快递小哥,讲快递放到丰巢,等自己有时间自己去取;这样双方都不需要等待对方,会提高你和快递小哥双方的效率;

(另外,网络的数据包的传输也是符合生产者-消费者模型的)

  1.  ThreadPoolExecutor的设计与实现

3.1 架构图

 

架构图解析(线程池的任务处理规则):

  1. 一个任务通过execute放进线程池;
  2. 首先会看看corePoolSize是否满(图中箭头1),如果没有,直接创建worker线程,执行该任务;
  3. 其次会看下BlockingQueue是否满(图中箭头2),如果没有,该任务直接放入BlockingQueue中,等待worker线程从BlockingQueue取出并执行;
  4. 再次会查看maxPoolSize是否满(图中箭头3),如果没有,会直接创建worker线程,并执行该任务;
  5. 最后,如果上述条件都不满足,则会执行Reject策略(图中箭头4),默认的实现是会丢弃任务;

 

3.2 类图


3.3.代码简析

流程图:

 

①提交任务代码流程:

 

分析:这里的任务处理策略,详细请看注释部分的代码;

 

②创建worker线程代码分析:

 

 

分析:在创建worker线程的时候,或拿到全局锁(mainLock),以保证并发安全;

③Worker任务处理代码分析:

 

分析:worker线程会轮询式的从阻塞队列获取任务并进行处理;

 

  1.  配置线程池参数

参数配置原则:

  1. 计算性的操作,建议设置接近于CPU核数的线程数量,否则会有非常多的线程上下文切换导致的额外开销,建议:N+1;
  2. IO阻塞想的操作,建议设置多余CPU核数的线程数量,否则线程阻塞会导致CPU空闲带来的CPU浪费,建议:2N+1;
  3. 混合性的操作,线程数量介于两者之间;
  1.  合理中断任务

核心原理:实际上Java是没有直接stop Thread的方法(之前的Thread.stop已经不支持了),因此Java都是通过中断的方式来进行取消/结束任务的;

线程中断分为两种情况:

  1. 阻塞的线程(如:Object.wait/LockSupport.park),可以通过Thread.interrupt来唤醒阻塞的线程,并抛出InterruptedException让程序员处理;
  2. 非阻塞线程,可以通过Thread.interrupt调用,然后判断Thread.isInterrupt来判断中断的标识符是否为true;

注:InterruptedException的异常是vm抛出的(因为Object.wait实际上是调用的Monitor::Wait来实现的线程阻塞),如图:

 

线程池中提供了shutdown/shutdownNow来关闭线程池,区别如下:

  1. Shutdown是运行阻塞队列先前的任务,并关闭空闲线程:

 

 

  1. shutdownNow是关闭线程,并不会再执行剩余的任务:

 

 

  1.  Executors的简单分析+涉及到的几种线程池
  1. newFixedThreadPool:固定线程处理任务;

 

  1. newSingleThreadExecutor:单个线程处理任务;

 

  1. newCachedThreadPool:来一个任务,创建一个线程处理.

 

  1. newScheduledThreadPool:定时的处理任务;

 

  1. newWorkStealingPool:工作窃取(参考之前的技术分享);

 

总结:很多线程池其实都是ThreadPoolExecutor实现的,只是配置参数不同罢了;

8. 遇到的问题(futuretask将异常吞掉的问题)

8.1 现象描述:

  1. 将一个Runnable 任务通过 submitOnComputationThread 到线程池中;
  2. Runnable内部有exception抛出,但是 没有任务异常堆栈信息。

8.2 问题分析:

FutureTask.java

 

run()捕获了异常,并最终通过 setException,设置到outcome中;

 

8.3 结论:

1.futureTask内部的run()方法,将异常截获,并通过setExcption保存异常结果;

2.需要通过 FutureTask 的get()方法,才能知道任务的失败获成功;

3.futureTask不管成功失败,都会最终调用 done()方法;

 

8.4 解决方案:

1.可以重写FutureTask 的done( ),在这里调用get(),即可把异常抛出;

2.重写 setException,直接抛出异常信息;

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值