【并发编程-3】线程池

对于多线程来说,new Thread一定是创建了线程,而Runnable只是一个任务,并没有创建新的线程。  所以,Runnable任务要交给线程来执行。 如果对于每个任务都创建一个线程来执行,显然是不合理的。
线程池就是为了复用线程来处理多个任务,像tomcat, dubbo,数据库连接池等常用的框架都会使用线程池。 在自己开发的过程中,也可能会使用多线程的情况。 比如,一个请求的步骤太多,可以使用多线程并行执行。

线程池继承关系:

 看两个核心类:ThreadPoolExecutor 和 ScheduledThreadPoolExecutor

ThreadPoolExecutor:

基本介绍:

//...

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 存放任务的阻塞队列
private final BlockingQueue<Runnable> workQueue;
// 对线程池内部各种变量进行互斥访问控制
private final ReentrantLock mainLock = new ReentrantLock();
// 线程集合
private final HashSet<Worker> workers = new HashSet<Worker>();
线程池中的每一个线程就是一个 Worker 对象:
private final class Worker extends AbstractQueuedSynchronizer implements
Runnable {
// ...
final Thread thread; // Worker封装的线程
Runnable firstTask; // Worker接收到的第1个任务
volatile long completedTasks; // Worker执行完毕的任务个数
// ...
}
由定义会发现, Worker 继承于 AQS ,也就是说 Worker本身就是一把锁,锁的就是worker对象本身。 lock 方法一旦获取了独占锁,表示当前线程正在执行任务中,那么它会有以下几个作用:
  1. 如果正在执行任务,则不应该中断线程;
  2. 如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断;
  3. 线程池在执行 shutdown 方法或 tryTerminate 方法时,会调用 interruptIdleWorkers 方法来中断空闲的线程,interruptIdleWorkers 中会使用 tryLock 方法来判断线程池中的线程是否是空闲状态。  说简单点,就是不能直接中断在任务中的线程,做到优雅的关闭。

核心参数: 

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
  1. corePoolSize:在线程池中始终维护的线程个数。
  2. maxPoolSize:在corePooSize已满、队列也满的情况下,扩充线程至此值。
  3. keepAliveTime/TimeUnitmaxPoolSize 中的空闲线程存活时间,销毁后线程数缩回corePoolSize
  4. blockingQueue:线程池所用的队列类型。
  5. threadFactory:线程创建工厂,可以自定义,有默认值:Executors.defaultThreadFactory() 
  6. RejectedExecutionHandlercorePoolSize已满,队列已满,maxPoolSize 已满,最后的拒绝策略。
优雅关闭线程池:
当关闭一个线程池的时候,有的线程还正在执行某个任务,有的调用者正在向线程池提交任务,并且队列中可能还有未执行的任务。因此,关闭过程需要一个平滑的过渡,这就涉及线程池的完整生命周期管理。
线程池状态:
线程池五种状态: RUNNING SHUTDOWN STOP TIDYING TERMINATED 。 关闭时,状态值只能从小到大迁移:
在调用 shutdown() 或者 shutdownNow() 之后,线程池并不会立即关闭,接下来需要调用 awaitTermination() 来等待线程池关闭。

awaitTermination中就是不断循环,每隔500ms去校验一下线程池状态是否为TERMINATED,如果是,则返回。 该状态就是上面说的,worker释放锁后就会更新状态。 

另外,shutdown()只会中断空闲的线程,shutdownNow()会中断所有线程,包括清空任务队列。 

 线程池执行过程:

  1. 执行execute方法向池中提交任务,如果核心线程数小于corePoolSize,即使池中还有其他空闲线程,也会创建新线程执行任务。
  2. 当达到corePoolSize,会放到队列中,空闲线程会从队列中取任务执行,在worker中其实就不断循环的过程。
  3. 队列已满,则会创建新线程,直到数量达到maxPoolSize,则会执行拒绝策略。
  4. 空闲时间超过keepAliveTime的线程,会被销毁,直到数量收缩到corePoolSize。 当然,如果线程池设置了allowCoreThreadTimeOut,只要空闲时间超过的线程都会被销毁。

线程池拒绝策略:

在线程池的execute方法的最后,也就是不满足执行条件时,就会执行拒绝策略。 创建线程池默认的handler是RejectedExecutionHandler =  new AbortPolicy()。

其实线程池提供了4种:

  • AbortPolicy:直接抛异常
  • CallerRunsPolicy:直接调用线程任务的run方法执行,线程池不参与(虽然线程池不再接收,但是线程任务可以自己执行)。
  • DiscardPolicy:直接丢弃任务,也不报错也不干啥,神不知鬼不觉
  • DiscardOldestPolicy:将队列中最早的一条丢弃,再将当前任务添加到队列中。
    • ​​​​​​​

线程池的队列:

ArrayBlockingQueue

        ArrayBlockingQueue(有界队列)是一个用数组实现的有界阻塞队列,按FIFO排序量。

LinkedBlockingQueue

        LinkedBlockingQueue(可设置容量队列)基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,可以一直添加,直到资源耗尽。

DelayQueue

        DelayQueue(延迟队列)是一个定时的延迟执行的队列。

SynchronousQueue

SynchronousQueue(直接提交队列),这种队列中不会保存任务,而是直接提交任务给线程处理。如果当前没有线程可用,则新建一个线程处理任务。

Executors: 

concurrent 包提供了 Executors 工具类,利用它可以创建各种不同类型的线程池。
单线程的线程池:

固定线程数的线程池: 

上面两种都是固定了线程数的,所以核心线程数和最大线程数一样多,因此,使用的队列是无边界的LinkedBlockingQueue,理论上可以一直放任务进去。

每接收一个任务就创建线程来执行,不往队列存放:

单线程具有周期调度功能的线程池: 

多线程具有周期调度功能的线程池:

通过以上多种场景下使用的线程池可以发现,各种线程池,无非就是使用不同的参数而已。  Executors还支持传其他参数的线程池,当然,也可以自己定义。

在《阿里巴巴 Java 开发手册》中,其实是明确禁止使用 Executors 创建线程池,并要求开发者自己使用 ThreadPoolExector或 ScheduledThreadPoolExecutor进行创建。这样做是为了强制开发者自己传参,明确线程池的运行策略,使其对线程池的每个配置参数皆做到心中有数,以规避因使用不当而造成资源耗尽的风险。 下面,具体说一下ScheduledThreadPoolExecutor:
ScheduledThreadPoolExecutor:
ScheduledThreadPoolExecutor也是ThreadPoolExector的子类,核心参数都一样,只是做了增强:
延迟执行任务:

有两个方法,可以是Runnable或Callable任务。 在经过delay的时间后,才执行任务。

 

周期性执行任务:

两个方法:

        scheduleAtFixedRate:按固定时间间隔执行,与任务本身的时间没有关系。 比如每隔5s执行一次当前任务。 但是它要求任务本身的执行时间要小于间隔时间,否则下一次开始的时候,上一次还没执行完。

        scheduleWithFixedDelay: 也是按固定的时间间隔执行,但是与任务本身的时间有关系。 比如间隔2s执行一次,任务本身的执行时间是10s,那么下一次执行时间就是12s的时候。

  • initialDelay:说系统启动后,需要等待多久才开始执行第一次。
  • period:间隔执行时间。

延迟执行任务依靠的是 DelayQueue BlockingQueue的一种,其实现原理是二叉堆。当然这里没有直接使用,而是自己实现了一个DelayedWorkQueue。
而周期性执行任务是执行完一个任务之后,再把该任务扔回到任务队列中,如此就可以对一个任务
反复执行。
​​​​​​​
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值