Spring中的線程池和定時任務功能

1.功能介紹

Spring框架提供了線程池和定時任務執行的抽象接口:TaskExecutor和TaskScheduler來支持異步執行任務和定時執行任務功能。同時使用框架本身定義的抽象接口來屏蔽掉底層JDK版本間以及Java EE中的線程池和定時任務處理的差別。
另外Spring還支持集成JDK內部的定時器Timer和Quartz Scheduler框架。web

2.線程池的抽象:TaskExecutor

TaskExecutor涉及到的相關類圖以下:
圖片描述
TaskExecutor接口源代碼以下所示:spring

public interface TaskExecutor extends Executor {

    /**
     * Execute the given {@code task}.
     * <p>The call might return immediately if the implementation uses
     * an asynchronous execution strategy, or might block in the case
     * of synchronous execution.
     * @param task the {@code Runnable} to execute (never {@code null})
     * @throws TaskRejectedException if the given task was not accepted
     */
    @Override
    void execute(Runnable task);

}

此接口和Executor幾乎徹底同樣,只定義了一個接收Runnable參數的方法,據Spring官方介紹此接口最初是爲了在其餘組建中使用線程時,將JKD抽離出來而設計的。在Spring的一些其餘組件中好比ApplicationEventMulticaster,Quartz都是使用TaskExecutor來做爲線程池的抽象的。數據庫

3.Spring提供的TaskExecutor的實現類

org.springframework.core.task.SimpleAsyncTaskExecutor

此實現支持任務的異步執行,可是此實現沒有線程的複用,每次執行一個提交的任務時候都會新建一個線程,任務執行完成後會將線程關閉,最大併發數默認是沒有限制的,可是能夠經過調用下面的方法來設置最大併發數。通常使用線程池來代替此實現,特別是執行一些生命週期很短的任務的時候。服務器

public void setConcurrencyLimit(int concurrencyLimit) {
        this.concurrencyThrottle.setConcurrencyLimit(concurrencyLimit);
    }

Spring還提供了同步任務執行的實現類:併發

org.springframework.core.task.SyncTaskExecutor

此類中只有一個方法,代碼以下:框架

@Override
    public void execute(Runnable task) {
        Assert.notNull(task, "Runnable must not be null");
        task.run();
    }

此方法中直接調用傳入的Runable對象的run方法,所以在執行此方法的時候不會另外開啓新的線程,只是普通的方法調用,同步執行提交的Runable對象。
Spring有兩個線程池的實現類,分別爲:SimpleThreadPoolTaskExecutor和ThreadPoolTaskExecutor,其中當咱們有Quarts和非Quarts共享同一個線程池的需求的時候使用SimpleThreadPoolTaskExecutor,除了這種狀況,咱們通常是使用
ThreadPoolTaskExecutor,此實現能夠經過屬性注入來配置線程池的相關配置。 ThreadPoolTaskExecutor中屬性注入的源碼以下:此配置能夠在運行期修改,代碼中修改過程使用了同步控制。dom

/**
 * Set the ThreadPoolExecutor's core pool size.
 * Default is 1.
 * <p><b>This setting can be modified at runtime, for example through JMX.</b>
 */
public void setCorePoolSize(int corePoolSize) {
    synchronized (this.poolSizeMonitor) {
        this.corePoolSize = corePoolSize;
        if (this.threadPoolExecutor != null) {
            this.threadPoolExecutor.setCorePoolSize(corePoolSize);
        }
    }
}

4.TaskExecutor使用Demo

首先定義一個任務以下所示:異步

public class DataSimulation implements Runnable{

  private HourAverageValueDao hourAverageValueDao;

  @Override
  public void run() {

      Random random = new Random();
      AverageValue averageValue = new AverageValue();
      averageValue.setAverageVaule(random.nextInt(100));
      averageValue.setCreatedTime(new Date());
      hourAverageValueDao.insert(averageValue);

  }
}

此任務中產生一個隨機數,並封裝成一個類對象,並將此數據插入到數據庫中。
而後須要定一個類,使用TaskExecutor,代碼以下:async

public class DataFacotory {

    private TaskExecutor executor;

    public TaskExecutor getExecutor() {
        return executor;
    }

    public void setExecutor(TaskExecutor executor) {
        this.executor = executor;
    }

    public void dataFactory(){

        for (int i =0; i < 10; i++){
            executor.execute(new DataSimulation());
        }
    }
}

此類中定義了TaskExecutor的屬性,並定一個方法,此方法中提交10個任務到TaskExecutor,下面只需配置Spring文件,注入TaskExecutor就能夠實現線程池的使用。配置文件以下所示:ide

<bean id = "taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
      <property name="corePoolSize" value = "5"></property>
      <property name = "maxPoolSize" value="10"></property>
      <property name="queueCapacity" value="25"></property>
  </bean>

完成配置後便可使用此線程池。Spring提供的線程池能夠經過配置文件配置線程池的配置,相比JDk自帶的線程池是一個很大的優點。

5.爲何使用線程池

1.經過使用線程池來實現線程的複用,減小線程建立和銷燬的開銷
2.將執行線程的任務交給線程池來操做,必定意義上實現瞭解耦
3.使用線程池能夠控制任務的最大併發數目,這個在防止內存溢出以及併發優化方面有很重要的做用。

6.定時任務抽象類:TaskScheduler

TaskScheduler接口源代碼以下:

public interface TaskScheduler {
  //經過觸發器來決定task是否執行
ScheduledFuture schedule(Runnable task, Trigger trigger);
//在starttime的時候執行一次
ScheduledFuture schedule(Runnable task, Date startTime);
從starttime開始每一個period時間段執行一次task
ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);
每隔period執行一次
ScheduledFuture scheduleAtFixedRate(Runnable task, long period);
從startTime開始每隔delay長時間執行一次
ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
每隔delay時間執行一次
ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);

}

指定開始時間的接口,若是時間已是過去的某一時間點,則此任務會立刻執行一次。以上幾種執行方式傳入Trigger的方式是用的最多的,Trigger接口中只定義了一個方法:

Date nextExecutionTime(TriggerContext triggerContext);

其中參數類型TriggerContext的定義以下:

public interface TriggerContext {

  /**
   * Return the last <i>scheduled</i> execution time of the task,
   * or {@code null} if not scheduled before.
   */
  Date lastScheduledExecutionTime();

  /**
   * Return the last <i>actual</i> execution time of the task,
   * or {@code null} if not scheduled before.
   */
  Date lastActualExecutionTime();

  /**
   * Return the last completion time of the task,
   * or {@code null} if not scheduled before.
   */
  Date lastCompletionTime();

}

提供了獲取上一次任務執行信息的接口。咱們經過實現Trigger接口能夠實現自定義觸發器來執行執行task。固然Spring也提供了兩個默認的實現類:PeriodicTrigger和CronTrigger。

7.TaskScheduler定時任務Demo

首先在Spring配置文件中啓用註解配置以下:

<task:annotation-driven scheduler="myScheduler"/> //指定scheduler屬性是可選項,不添加也能夠正常使用
<task:scheduler id="myScheduler" pool-size="10"/>

而後建立service,並在service中使用@Scheduled註解建立定時任務,代碼以下:

@Component
public class SchedulerPoolTest {

  @Scheduled(cron = "0 * * * * ?")
  public void task1(){
      System.out.println("test");
      Thread thread =  Thread.currentThread();
      System.out.println("ThreadName:" + thread.getName() + ",id:" + thread.getId() + ",group:" + thread.getThreadGroup());

  }

  @Scheduled(fixedDelay = 5000)
  public void task2(){
      System.out.println("test");
      Thread thread =  Thread.currentThread();
      System.out.println("ThreadName:" + thread.getName() + ",id:" + thread.getId() + ",group:" + thread.getThreadGroup());

  }

}

只是添加以上內容可能還不能正常執行task,還須要注意如下兩點:

1.必須將SchedulerPoolTest類包含在spring所掃描的包裏面
配置以下:

<context:component-scan base-package="com.zjut.task" />

2.須要在web.xml中添加spring配置文件的監聽器,代碼以下:

<context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>classpath*:spring-task.xml</param-value>
 </context-param>

 <listener>
   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>

添加以上內容後,啓動服務器,將會定時執行任務。

8.Cron表達式

Cron表達式由6個字符串組成,每一個字符串分別表明:

{秒} {分} {時} {日} {月} {周}

其中每一個字符串所容許的取值範圍爲:

字段名                 容許的值                        容許的特殊字符  
秒                    0-59                            , - * /  
分                    0-59                            , - * /  
小時                  0-23                            , - * /  
日                    1-31                            , - * ? / L W C  
月                    1-12 or JAN-DEC                 , - * /  
周幾                  1-7 or SUN-SAT                   , - * ? / L C #

經常使用的Cron表達式:

"0 10,44 14 ? 3 WED" 每一年三月的星期三的下午2:10和2:44觸發
"0 0/5 14,18 * * ?" 在天天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發
"15-30/5 * * * * ?" 每分鐘的15秒到30秒之間開始觸發,每隔5秒觸發一次
"0 15 10 ? * 5#3" 每月第三週的星期四的10點15分0秒觸發任務

注:問號是用於避免日和周的設定由衝突而用的,當其中一個設置了具體的值,另一個必須使用?。另外推薦一個Cron表達式生成的連接:http://www.cronmaker.com/

9.@Async註解

Async註解提供了異步調用方法的功能,當調用由此註解的方法的時候方法調用者會立刻返回而不會等待調用的方法執行完成,被調用的方法會從線程池中分配一個線程來執行此方法。

10.Spring定時任務中併發執行的問題

同一個任務,當上一個任務沒有執行完成的時候,新的任務不會執行。不一樣任務的狀況下:TODO...

相關文章
相關標籤/搜索