首頁 >Java >java教程 >Java DelayQueue怎麼實現延時任務

Java DelayQueue怎麼實現延時任務

WBOY
WBOY轉載
2023-05-13 16:40:061796瀏覽

一、DelayQueue的應用原理

DelayQueue是一個無界的BlockingQueue的實作類,用來放置實作了Delayed介面的對象,其中的物件只能在其到期時才能從佇列中取走。

BlockingQueue即阻塞佇列,java提供的面向多執行緒安全的佇列資料結構,當佇列內元素數量為0的時候,試圖從佇列內取得元素的執行緒將會被阻塞或拋出例外。

這裡的「無界」隊列,是指隊列的元素數量不存在上限,隊列的容量會隨著元素數量的增加而擴容。

Java DelayQueue怎麼實現延時任務

DelayQueue實作了BlockingQueue接口,所以具有無界、阻塞的特點,除此之外它自己的核心特點就是:

##“放入該佇列的延時任務對象,只要到達延時時間之後才能被取到”

DelayQueue 不接收null元素

「DelayQueue 只接受那些實作了java.util.concurrent.Delayed介面的物件」

#二、訂單延時任務的實作

了解了DelayQueue的特點之後,我們就可以利用它來實現延時任務了,實作

java.util.concurrent.Delayed介面。

import org.jetbrains.annotations.NotNull;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * 延时订单任务
 */
public class OrderDelayObject implements Delayed {
  private String name;
  private long delayTime;   //延时时间
  //实际业务中这里传订单信息对象,我这里只做demo,所以使用字符串了
  private String order;

  public OrderDelayObject(String name, long delayTime, String order) {
    this.name = name;
    //延时时间加上当前时间
    this.delayTime = System.currentTimeMillis() + delayTime;
    this.order = order;
  }

  //获取延时任务的倒计时时间
  @Override
  public long getDelay(TimeUnit unit) {
    long diff = delayTime - System.currentTimeMillis();
    return unit.convert(diff, TimeUnit.MILLISECONDS);
  }

  //延时任务队列,按照延时时间元素排序,实现Comparable接口
  @Override
  public int compareTo(@NotNull Delayed obj) {
    return Long.compare(this.delayTime, ((OrderDelayObject) obj).delayTime);
  }

  @Override
  public String toString() {
    Date date = new Date(delayTime);
    SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    return "\nOrderDelayObject:{"
            + "name=" + name
            + ", time=" + sd.format(date)
            + ", order=" + order
            + "}";
  }
}

上文類別中的order為訂單資訊對象,在實際的業務開發過程中應該是傳遞訂單訊息,用於取消訂單業務的實現(訂單30分鐘不付款自動取消)。

Delayed介面繼承自 Comparable接口,所以需要實作compareTo方法,用於延時任務在佇列中依照「延時時間」進行排序。

getDelay方法是Delayed介面方法,實作該方法提供取得延時任務的倒數計時時間

三、訂單處理

首先我們需要一個容器,永久保存延時任務隊列,如果是Spring開發環境我們可以這麼做。

@Bean("orderDelayQueue")
public DelayQueue<OrderDelayObject> orderDelayQueue(){
    return new DelayQueue<OrderDelayObject>();
}

當用戶下單的時候,將訂單下單任務放入延時隊列

@Resource
private DelayQueue<OrderDelayObject> orderDelayQueue;

//发起订单下单的时候将订单演示对象放入orderDelayQueue
orderDelayQueue.add(
        new OrderDelayObject(
                "订单延时取消任务",
                30 * 60 * 1000,    //延时30分钟
                "延时任务订单对象信息"
        )
);

系統內開啟一個線程,不斷的從隊列中獲取訊息,獲取到之後對延時訊息進行處理。

DelayQueue的take方法從佇列中取得延時任務對象,如果佇列元素數量為0,或沒有到達“延時時間的任務”,則該執行緒會被阻塞。

@Component
public class DelayObjectConsumer  implements InitializingBean {

  @Resource
  private DelayQueue<OrderDelayObject> orderDelayQueue;

  @Override
  public void afterPropertiesSet() throws Exception {
    while (true) {
      OrderDelayObject task = orderDelayQueue.take();
      System.out.println(task.toString());
      System.out.println(task.getOrder());
      //根据order订单信息,去查询该订单的支付信息
      //如果用户没有进行支付,将订单从数据库中关闭
      //如果订单并发量比较大,这里可以考虑异步或线程池的方式进行处理
    }
  }
}

需要說明的是,這裡的while-true循環的延時任務處理是順序執行的,在訂單並發量比較大的時候,需要考慮非同步處理的方式完成訂單的關閉操作。我之前寫過一個SpringBoot的可觀測、易配置的線程池開源項目,可能會對你有幫助,源代碼地址

經過我的測試,放入orderDelayQueue的延時任務,在半小時之後得到正確的執行處理。說明我們的實作是正確的。

四、優缺點

使用DelayQueue實現延時任務非常簡單,而且簡便,全部都是標準的JDK程式碼實現,不用引入第三方依賴(不依賴redis實作、訊息佇列實現等),非常的輕量級。

它的缺點就是所有的操作都是基於應用記憶體的,一旦出現應用單點故障,可能會造成延時任務資料的遺失。如果訂單並發量非常大,因為DelayQueue是無界的,訂單量越大,佇列內的物件就越多,可能造成OOM的風險。所以使用DelayQueue實現延時任務,只適用於任務量較小的情況。

以上是Java DelayQueue怎麼實現延時任務的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除