zoukankan      html  css  js  c++  java
  • 多线程之----线程互斥

    接上一篇关于多线程中Timer定时器的用法。

    今天在开始关于线程的互斥之前,先对另外一个定时器spring提供的qurtar的用法做一个简单的介绍,同时对比一下与java原生态提供的Timer的区别。

    先上一个定时任务的配置吧,这是我们自己的项目中定时任务删除每个表记录的:

    对于具体的任务的业务实现我们不说了,我就说一下quartor是如何调度的。在开始解读源码之前,我们先借用其他网友的观点对这个进行一个概要的介绍:

    1. Java定时器没有持久化机制。这个在上一篇中看的TIMER的源码确实没有

    2. Java定时器的日程管理不够灵活(只能设置开始时间、重复的间隔,设置特定的日期、时间等)//这点感同身受
    3. Java定时器没有使用线程池(每个Java定时器使用一个线程)//想必在用timer是遇到了吧。
    4. Java定时器没有切实的管理方案,你不得不自己完成存储、组织、恢复任务的措施

    反正就是什么呢,Timer比较单一,如果是一些简单的任务可以使用,企业级的一半还是使用quartor。

    好了 下面咱们开始quartot源码的读吧。

    读源码之前需要增加一个知识点:

    1.InitializingBean 与 afterPorpertiesSet()

    关于在spring  容器初始化 bean 和销毁前所做的操作定义方式有三种:

    第一种:通过@PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作

    第二种是:通过 在xml中定义init-method 和  destory-method方法

    第三种是: 通过bean实现InitializingBean和 DisposableBean接口

    Spirng的InitializingBean为bean提供了定义初始化方法的方式。InitializingBean是一个接口,它仅仅包含一个方法:afterPropertiesSet()。   在spring 初始化后,执行完所有属性设置方法(即setXxx)将自动调用 afterPropertiesSet(), 在配置文件中无须特别的配置, 但此方式增加了bean对spring 的依赖,应该尽量避免使用。

    实现org.springframework.beans.factory.DisposableBean接口允许一个bean,可以在包含它的BeanFactory销毁的时候得到一个回调。DisposableBean也只指定了一个方法:

    void destroy() throws Exception;

    在我使用的这里使用的是第三种,这也就是为什么在我们的定时任务配置文件中看不到init-method的配置的源码。

    好了这时候我们看源码,先看SchedulerFactoryBean来创建scheduler实例的类的定义

    在这个定义中我们看到SchedulerFactoryBean是显现了initializingBean这个接口和DisposableBean接口,我们从上面的知识点中可以知道,在实现了initilizingBean这个接口的类中,会实现一个方法afterPropertiesSet()。执行这个方法是时机是spring在set所有属性完成后,

    下面是借鉴的一个benzero的网友的内容:在我写这个博客的时候网上搜了一下,感觉这哥们写的真是好,我写的话并一定比人家的好,哈哈哈  好东西用来共享啦。但是要标明引自哪里,毕竟有版权啊。

     具体来分析afterPropertiesSet()和start(),可以看出afterPropertiesSet()的核心步骤:

     

    1. 实例化一个SchdulerFactory, 此处使用的是默认的StdSchedulerFactory.class

     

    2. 通过Factory获取一个scheduler实例容器。

     

    3. 配置/添加 对该容器指定的listener、jobs、triggers等,本质是将注入的上述属性配合到上步创建的Scheduler容器当中,比较简单,后续忽略介绍。

     

      代码说明的截图:

    start()择更为简单,直接启动上面创建的scheduler容器的实例,代码说明截图如下:其中有一个startupDelay的参数,即启动延时,默认都是为0的,一般也是需要即时启动的。

     

    综上,可以看到quatz启动的大概步骤, 利用spring提供的两个回调接口, 分别在bean实例的构造和容器启动阶段实现了 Scheduler容器的构造 和 scheduler的启动。

        当然理解架构只到此程度是远远不够的,上述部分的核心是通过Factory获取到一个scheduler实例,即createScheduler()方法,下一节对这个过程做出详细的说明。

     

     

    第二节.scheduler容器获取(构造)过程

     

     首先,createScheduler()之中的主流程调用树结构如下,其核心是在instantiate()完成的,外部的2层主要做了些读取配置和条件判断等。

    直接来看instantiate()的代码,类比较大且复杂,只列出核心行

     

     

     

    1.创建了一个线程池,默认使用的是SimpleThreadPool, 代码中tp对象

     

    2.创建了一个jobStore,默认使用的是RAMJobStore, 代码中的js对象

     

    3.创建一个QuartzScheduler对象, 代码中的qs对象,  下图列出他的创建树。

     

     大体上有4步,有2个关键点需要掌握

     

      a.QuartzScheduler对象保存有QuartzSchedulerThread的引用,实际的调度工作基本上是由QuartzSchedulerThread这个对象做的. 当然实例化QuartzScheduler的目的主要也在于去实例化QuartzSchedulerThread。

     

      b.QuartzSchedulerThread在构造函数中实例化,并一经创建就自启动,它是一个独立的线程,至于自启后做什么,在后面章节说明。下面是quartzScheduler的创建过程

                  

     

    4.创建了scheduler. 

      4.1.  QuartzSchedulerResources 对象,代码中的rsrcs对象

           Contains all of the resources,可以将其视为资源缓存对象,包含了所有定时模块的资源的引用。

      4.2. Scheduler对象。 看源代码它的直接实例是StdScheduler,但实际StdScheduler中直接引用了QuartzScheduler(qs)且仅止于此,没有额外的动作。 简单的你可以认为这是一层封装。因此你可以认为真正在工作的其实就是QuartzScheduler或者说是 QuartzSchedulerThread

    本节主要讲述了整个scheduler容器创建时的过程,以及4个重要的步骤节点。后续章节来分别详述这些核心步骤的具体创建和工作原理。

     

    至此,通过SchedulerFactory构建Scheduler的过程已完成,可以总结为三个要点。

     

    1. 创建线程池

     

    2. 创建JobStore

     

    3. 通过QuartzScheduler启动一个QuartzSchedulerThread 

     

    他们的关系如下图,最终返回的这是QuatzScheduler的引用。本图转自网络。

    第三节. 对第二节的一个小结

       通过对上节真个启动过程的源码解读,应该可以较为深入的理解spring 管理的Quatz的启动流程,网络上一张时序图可以作为很好的参考:

     

     

     

     

    第四节.QuartzSchedulerThread是如何工作的。

        在第二节中讲到启动是一个核心步骤就是创建QuartzSchedulerThread并启动它,因为它是继承自Thread的,因此我们可以跟踪它的run方法看它究竟做哪些工作

         

     

    解析这个方法如下:

    1. 方法体是一个无限循环,一直在等任务的出现,然后启动执行。

    2. 刚被启动时,是在等pause信号量。 这个信号在何时改变了,也行你已经在上面时序图中看到了, 前面章节提到的start()的实现其本质的工作就是修改这个pause信号量。 使

    QuartzSchedulerThread正式进入工作状态(虽然其构造时就被启动,但没有真正开始工作)。

    3.总会从jobstore里找出下个应该执行的trigger任务(具体步骤后面解析),然后等待这一时刻到来。(通过不停的比较trigger的时间和当前系统时间)

    4. 之后通过jobstore将其对应的jobdetail封装成一个runShell (具体步骤后面解析)

    5. 将runShell交给线程池去执行,runInThread方法,下节会具体讲解。

    6. 继续循环寻找下一个执行trigger,依次往复。

    因此上面提到过其实在quartz里面,真正做实际工作的调度线程其实就是它。

     

     

    第五节.SimpleThreadPool的介绍

     

     

         总体来讲,SimpleThreadPool的实现很简单。

     

         第2节介绍createScheduler过程时,提到第一个步骤便是创建一个线程池,该默认的线程池便是SimpleThreadPool. 跟踪源代码可见,最然构建tp实例时未做任何操作,但在实例之后,又调用了SimpleThreadPool.initialize(), 此方法相当于初始配置tp以及启用它。下面来分析源码

    a.代码比较简单,注意一点是创建的线程是WorkerThread,因此看具体工作还需要追踪到WorkerThread里面。

     

    B.WorkerThread是一个内部类,看下被启动是具体做什么工作(run方法)。

       比较简单,“一直不停的在等待runable类型的事件的注入,一旦进来,就执行”。

     

       本节和上节可以结合在一起归纳一下,QuartzSchedulerThread一直寻找可执行目标,找到后就交给SimpleThreadPool具体执行, 而相应的SimpleThreadPool的线程们也被设计成随时等待runnable事件的注入。举个通俗的例子,QuartzSchedulerThread就是青楼的老妈子,SimpleThreadPool

    里的线程就是待接客的姑娘们,老妈子负责不断寻找/接待客人,然后将目标客户引给闲置的姑娘, 具体的接待工作由姑娘完成。

         现在提出两个问题,“客人”是如何被找到的? “姑娘”是如何开始接待的? 在下节讨论。

     

    6.RAMJobStore的介绍

     

          JobStore负责保存所有配置到调度器里的工作数据:jobs,triggers,calendars等等,“RAM”顾名思义就是利用内存来持久化调度程序信息。这种作业存储类型最容易配置、构造和 运行,但是当应用程序停止运行时,所有调度信息将被丢失。RAMJobStore是spring+quatz默认的jobstore

     

    在第4节中讲解QuartzSchedulerThread的工作原理中多次提到了jobStore,其中有一个步骤便是通过jobStore获取下一个应该执行的trigger,现在就来具体分析。

     

     

    A. 利用jobStore获取下一个需要执行的trigger, 此处调用的是acquireNextTrigger()方法。 注意一点,在这步中一般情况下是先将第一个执行的trigger从treeset中删除,然后返回它。 treeset是按照触发是每个trigger最近一次应该执行的时间排序的。 大概原理是这样的,1.往队列添加trigger的时候,是以该trigger的下次触发时间来

    确定它在treeset的顺序 2.要执行trigger时,是直接将trigger移出treeset. 3.在恰当的时刻,将该trigger按照第一步加入到treeset

    B.上面的一点你可能会思考一个问题,这个trigger的下一次是怎么触发的(因为已经从timeTriggers队列里删除了)。

      实际上在第四节的代码讲解中有一句注释的代码,当时没有提及,因这代码前后都有其他逻辑,所以是以注释的形式来展现的。 在此处说明比较合适。 

    此方法包含了如下操作, 当然包含了诸多其他内容,此处不展开说明:

     根据当前的nextFireTime来实现自我更新, 然后将此trigger重新加入到timeTriggers。

      而此方法的返回值bndle在C中详解。

     

    C. 第四节讲到拿到Trigger的下一步是创建一个jobshell丢给线程池去执行。现在看看jobShell是如何创建的。

    首先B中 triggerFired方法返回的bndle对象,它是TriggerFiredBundle类型,该类属性如下,实质上这些数据也是从jobStore那里引用过来的,封装成一个对象方便后续处理。

     

    创建runshell的数据来源正是基于这个对象的。 创建runshell实例很简单,对实例的初始化过程主要是由JobRunShell.initialize()方法负责的。具体分析:

     

    runShell创建并初始化之后,是被丢进ThreadPool中执行的,而执行过程就是线程调用runShell的run()方法, 大家可以具体查看,这里由于我整理的文档涉及

    我们项目内容,省略掉这一部分。

     

     

    文档至此,你应当比较全面的了解quatz的初始化过程以及调用机制

     

    三. Spring+quatz的参考文档

     

    以下参考文档分别从各方面侧重介绍了quatz,其中主要参考了《quartz学习笔记》

     

    http://jinnianshilongnian.iteye.com/blog/1902886   详解spring事件驱动模型

     

    http://www.cnblogs.com/yunxuange/archive/2012/08/28/2660141.html quatz 学习笔记

     

    http://www.cnblogs.com/chanedi/p/4169510.html   Quartz的线程池解析

     

    http://blog.csdn.net/luccs624061082/article/details/39288275 Spring 之生命周期机制混合使用

     

    http://www.cnblogs.com/langtianya/archive/2013/05/15/3079109.html  quatz详解

     

    http://seanhe.iteye.com/blog/691835  Spring对Quartz的封装实现简单分析及使用注意事项

    文章部分引自 http://www.cnblogs.com/surprizeFuture/articles/4564293.html

  • 相关阅读:
    innodb下ibd文件组成
    redo在ACID中作用,及一些概念
    mysql innodb安装目录下文件介绍: 日志记录redu/undo log及临时表ibtmp1
    mysql innodb引擎独立表空间记录,表组成及表迁移
    Python之函数、递归、内置函数
    Python之列表、字典、集合
    Python之介绍、基本语法、流程控制
    CSS之Bootstrap(快速布局)
    Django之缓存
    python之class面向对象(进阶篇)
  • 原文地址:https://www.cnblogs.com/duanxiaojun/p/6605153.html
Copyright © 2011-2022 走看看