线程池的实现原理

线程池的实现原理,分为以下几个方面:
    1.线程池状态
    2.任务的执行
    3.线程池中的线程初始化
    4.任务缓存队列及排队策略
    5.任务拒绝策略
    6.线程池的关闭
    7.线程池容量的动态调整

具体的成员变量定义如下 :
private final BlockingQueue<Runnable> workQueue; // 任务缓存队列,用来存放等待执行的任务

private final ReentrantLock mainLock = new ReentrantLock(); // 线程池的主要状态锁,对线程池的状态(比如线程池的大小,状态等)的改变都要使用这个锁

private final HashSet<Worker> workers = new HashSet<Worker>(); // 用来存放工作集

private volatile long keepAliveTime; //线程存活时间

private volatile boolean allowCoreThreadTimeOut; // 是否允许为核心线程设置存活时间

private volatile int corePoolSize; // 核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)

private volatile int maximumPoolSize; // 线程池最大能容忍的线程数

private volatile int poolSize; // 线程池中当前的线程数

private volatile RejectedExecutionHandler handler; // 任务拒绝策略

private volatile ThreadFactory threadFactory; // 线程工厂,用来创建线程

private int largestPoolSize; // 用来记录线程池中曾经出现过的最大线程数

private long completedTaskCount; //用来记录已经执行完毕的任务个数
corePoolSize:核心运行的poolSize,也就是当超过这个范围的时候,就需要将新的Thread放入到等待队列中了;

maximumPoolSize:一般你用不到,当大于了这个值就会将Thread由一个丢弃处理机制来处理,但是当你发生:newFixedThreadPool的时候,corePoolSize和maximumPoolSize是一样的,而corePoolSize是先执行的,所以他会先被放入等待队列,而不会执行到下面的丢弃处理中,看了后面的代码你就知道了。

workQueue:等待队列,当达到corePoolSize的时候,就向该等待队列放入线程信息(默认为一个 LinkedBlockingQueue

运行中的队列属性为:workers,为一个HashSet;内部被包装了一层,后面会看到这部分代码。

keepAliveTime:默认都是0,当线程没有任务处理后,保持多长时间,cachedPoolSize是默认60s,不推荐使用。

threadFactory:是构造Thread的方法,你可以自己去包装和传递,主要实现newThread方法即可;

handler:也就是参数 maximumPoolSize达到后丢弃处理的方法, Java提供了5种丢弃处理的方法,当然你也可以自己弄,主要是要实现接口:RejectedExecutionHandler中的方法:

public void  rejectedExecution( Runnabler,  ThreadPoolExecutor e)

java默认的是使用:AbortPolicy,他的作用是当出现这中情况的时候会抛出一个异常;其余的还包含:

1、 CallerRunsPolicy:如果发现线程池还在运行,就直接运行这个线程

2、 DiscardOldestPolicy:在线程池的等待队列中,将头取出一个抛弃,然后将当前线程放进去。

3、 DiscardPolicy:什么也不做

4、 AbortPolicy:java默认,抛出一个异常:RejectedExecutionException。

通常你得到线程池后,会调用其中的: submit方法或 execute方法去操作;其实你会发现,submit方法最终会调用execute方法来进行操作,只是他提供了一个Future来托管返回值的处理而已,当你调用需要有返回值的信息时,你用它来处理是比较好的;这个Future会包装对Callable信息,并定义一个Sync对象(),当你发生读取返回值的操作的时候,会通过Sync对象进入锁,直到有返回值的数据通知。
 
线程池的几个细节:
  • 工作者线程worker:即线程池中可以重复利用起来执行任务的线程,一个worker的生命周期内会不停的处理多个业务job。线程池“复用”的本质就是复用一个worker去处理多个job,“流控“的本质就是通过对worker数量的控制实现并发数的控制。通过设置不同的参数来控制worker的数量可以实现线程池的容量伸缩从而实现复杂的业务需求
  • 待处理工作job的存储队列:工作者线程workers的数量是有限的,同一时间最多只能处理最多workers数量个job。对于来不及处理的job需要保存到等待队列里,空闲的工作者work会不停的读取空闲队列里的job进行处理。基于不同的队列实现,可以扩展出多种功能的线程池,如定制队列出队顺序实现带处理优先级的线程池、定制队列为阻塞有界队列实现可阻塞能力的线程池等。流控一方面通过控制worker数控制并发数和处理能力,一方面可基于队列控制线程池处理能力的上限。
  • 线程池初始化:即线程池参数的设定和多个工作者workers的初始化。通常有一开始就初始化指定数量的workers或者有请求时逐步初始化工作者两种方式。前者线程池启动初期响应会比较快但造成了空载时的少量性能浪费,后者是基于请求量灵活扩容但牺牲了线程池启动初期性能达不到最优。
  • 处理业务job算法:业务给线程池添加任务job时线程池的处理算法。有的线程池基于算法识别直接处理job还是增加工作者数处理job或者放入待处理队列,也有的线程池会直接将job放入待处理队列,等待工作者worker去取出执行。
  • workers的增减算法:业务线程数不是持久不变的,有高低峰期。线程池要有自己的算法根据业务请求频率高低调节自身工作者workers的数量来调节线程池大小,从而实现业务高峰期增加工作者数量提高响应速度,而业务低峰期减少工作者数来节省服务器资源。增加算法通常基于几个维度进行:待处理工作job数、线程池定义的最大最小工作者数、工作者闲置时间。

几种线程池的实现细节:
        JDK:
                使用了HashSet来存储工作者workers,通过可重入锁ReentrantLock对其进行并发保护。每个work都是一个Runnable接口。使用了实现接口BlockingQueue的阻塞队列存储待处理工作job,可使用JDK自身的同步阻塞队列(同步阻塞队列:SynchronousQueue,有界队列:ArrayBlockingQueue,无界队列:LinkedBlockingQueue,优先级队列:PriorityBlockingQueue)
        Jetty6:
                同样使用了HashSet存储工作者workers,通过synchronized一个对象进行HashSet的并发保护。使用数组存储了待处理的job对象。超过数组最大值时扩容。
        Jetty8:
                使用了ConcurrentLinkedQueue存储工作者workers,利用JDK基于CAS算法的实现提高了并发效率。针对ConcurrentLinkedQueue无法保证size()实时性问题引入原子变量AtomicInteger统计工作者数量。
         

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值