【夜话系列】DelayQueue延迟队列(下):实战应用与面试精讲

🔥 本文是DelayQueue系列的下篇,聚焦实战应用场景和性能优化。通过多个真实案例,带你掌握DelayQueue在项目中的最佳实践和性能调优技巧。

📚 系列专栏推荐

在这里插入图片描述

文章目录

一、DelayQueue实战应用

1.1 订单超时自动取消

1.1.1 业务场景分析

在电商系统中,订单创建后通常需要在一定时间内完成支付,否则系统会自动取消订单并释放库存。这是一个典型的延时任务场景:

  • 订单创建后,需要设置一个倒计时(通常为15分钟或30分钟)
  • 如果在倒计时结束前完成支付,需要取消该延时任务
  • 如果倒计时结束时订单仍未支付,则自动取消订单
  • 系统需要支持大量并发订单的超时管理

传统实现方式通常采用定时任务扫描数据库,但这种方式存在以下问题:

  • 数据库压力大,特别是订单量大的场景
  • 实时性不够,可能出现几秒甚至几分钟的延迟
  • 资源消耗高,需要频繁扫描数据库

使用DelayQueue可以很好地解决这些问题,实现内存级的订单超时管理。

1.1.2 实现方案设计

基于DelayQueue的订单超时取消方案设计如下:

  1. 创建一个实现Delayed接口的订单超时任务类
  2. 维护一个全局的DelayQueue,用于管理所有未支付订单的超时任务
  3. 订单创建时,向DelayQueue中添加对应的超时任务
  4. 订单支付成功时,从DelayQueue中移除对应的超时任务
  5. 启动专门的线程从DelayQueue中获取到期的任务,执行订单取消逻辑

这种设计的优势在于:

  • 内存级处理,性能高
  • 精确的超时控制,无需频繁扫描数据库
  • 支持动态取消超时任务
  • 系统重启后可以通过数据库中的订单状态和创建时间重建延时队列
1.1.3 完整代码示例
import java.util.Map;
import java.util.concurrent.*;

/**
 * 基于DelayQueue实现的订单超时自动取消功能
 */
public class OrderTimeoutCancelService {
   
    // 订单超时时间,单位毫秒
    private final long ORDER_TIMEOUT = 30 * 60 * 1000; // 30分钟
    
    // 延迟队列,用于处理订单超时
    private final DelayQueue<OrderDelayTask> delayQueue = new DelayQueue<>();
    
    // 用于存储订单与任务的映射关系,便于取消任务
    private final Map<String, OrderDelayTask> taskMap = new ConcurrentHashMap<>();
    
    // 订单服务,实际业务中通过依赖注入获取
    private final OrderService orderService;
    
    public OrderTimeoutCancelService(OrderService orderService) {
   
        this.orderService = orderService;
        // 启动处理线程
        new Thread(this::processTimeoutOrders).start();
    }
    
    /**
     * 添加订单超时任务
     * @param orderId 订单ID
     */
    public void addOrderTimeoutTask(String orderId) {
   
        // 创建超时任务
        OrderDelayTask task = new OrderDelayTask(orderId, ORDER_TIMEOUT);
        // 添加到延迟队列
        delayQueue.offer(task);
        // 保存映射关系
        taskMap.put(orderId, task);
        
        System.out.println("订单[" + orderId + "]加入超时队列,将在" 
                + ORDER_TIMEOUT/1000 + "秒后自动取消");
    }
    
    /**
     * 订单支付成功,取消超时任务
     * @param orderId 订单ID
     */
    public void orderPaid(String orderId) {
   
        OrderDelayTask task = taskMap.remove(orderId);
        if (task != null) {
   
            // 从队列中移除任务(这里利用了equals方法判断)
            delayQueue.remove(task);
            System.out.println("订单[" + orderId + "]已支付,取消超时任务");
        }
    }
    
    /**
     * 处理超时订单的线程任务
     */
    private void processTimeoutOrders() {
   
        System.out.println("订单超时处理线程已启动");
        while (true) {
   
            try {
   
                // 获取超时的订单任务
                OrderDelayTask task = delayQueue.take();
                // 从映射中移除
                taskMap.remove(task.getOrderId());
                // 执行订单取消逻辑
                orderService.cancelOrder(task.getOrderId(), "订单超时未支付");
                
                System.out.println("订单[" + task.getOrderId() + "]超时未支付,已自动取消");
            } catch (InterruptedException e) {
   
                Thread.currentThread().interrupt();
                break;
            } catch (Exception e) {
   
                // 处理异常,实际项目中应该有更完善的异常处理
                System.err.println("处理超时订单异常:" + e.getMessage());
            }
        }
    }
    
    /**
     * 订单延迟任务
     */
    static class OrderDelayTask implements Delayed {
   
        private final String orderId;
        private final long expireTime; // 过期时间,单位:毫秒
        
        public OrderDelayTask(String orderId, long delayTime) {
   
            this.orderId = orderId;
            this.expireTime = System.currentTimeMillis() + delayTime;
        }
        
        public String getOrderId() {
   
            return orderId;
        }
        
        @Override
        public long getDelay(TimeUnit unit) {
   
            return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }
        
        @Override
        public int compareTo(Delayed o) {
   
            return Long.compare(this.expireTime, ((OrderDelayTask) o).expireTime);
        }
        
        @Override
        public boolean equals(Object obj) {
   
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            OrderDelayTask that = (OrderDelayTask) obj;
            return orderId.equals(that.orderId);
        }
        
        @Override
        public int hashCode() {
   
            return orderId.hashCode();
        }
    }
    
    /**
     * 模拟订单服务接口
     */
    interface OrderService {
   
        void cancelOrder(String orderId, String reason);
    }
    
    /**
     * 测试代码
     */
    public static void main(String[] args) throws InterruptedException {
   
        // 模拟订单服务实现
        OrderService orderService = (orderId, reason) -> 
            System.out.println("执行订单[" + orderId + "]取消操作,原因:" + reason);
        
        // 创建订单超时服务
        OrderTimeoutCancelService service = new OrderTimeoutCancelService(orderService);
        
        // 模拟创建3个订单
        service.addOrderTimeoutTask("ORDER_001");
        service.addOrderTimeoutTask("ORDER_002");
        service.addOrderTimeoutTask("ORDER_003");
        
        // 模拟1秒后支付了订单2
        Thread.sleep(1000);
        service.orderPaid("ORDER_002");
        
        // 等待所有订单处理完成
        Thread.sleep(35000);
    }
}
1.1.4 注意事项与优化点
  1. 任务去重

    • 重写了equals和hashCode方法,确保可以根据订单ID正确移除任务
    • 使用ConcurrentHashMap存储订单ID与任务的映射,便于快速查找和取消任务
  2. 异常处理

    • 处理线程中捕获所有异常,避免因单个任务异常导致整个处理线程终止
    • 实际项目中应该添加更完善的日志记录和异常处理机制
  3. 系统重启恢复

    • 系统重启后,内存中的DelayQueue会丢失所有任务
    • 解决方案:系统启动时,从数据库加载所有未支付且未超时的订单,重新加入DelayQueue
    // 系统启动时恢复未处理的订单超时任务
    public void recoverOrderTasks() {
         
        List<Order> pendingOrders = orderService.findPendingPaymentOrders();
        for (Order order : pendingOrders) {
         
            // 计算剩余超时时间
            long createTime = order.getCreateTime().getTime();
            long now = System.currentTimeMillis();
            long remainTimeout = createTime + ORDER_TIMEOUT - now;
            
            // 如果订单还未超时,则加入延迟队列
            if (remainTimeout > 0) {
         
                OrderDelayTask task = new OrderDelayTask(order.getOrderId(), remainTimeout);
                delayQueue.offer(task);
                taskMap.put(order.getOrderId(), task);
            } else {
         
                // 已超时但未处理的订单,直接执行取消逻辑
                orderService.cancelOrder(order.getOrderId(), "系统重启,订单超时未支付");
            }
        }
        System.out.println("成功恢复" + pendingOrders.size() + "个未支付订单的超时任务");
    }
    
  4. 性能优化

    • 使用线程池替代单个线程处理超时订单,提高并发处理能力
    • 批量处理超时订单,减少数据库操作次数
    • 考虑使用分布式延迟队列,解决单机容量和可靠性问题

1.2 限时优惠券管理

1.2.1 优惠券过期处理

电商和营销系统中,限时优惠券是常见的营销手段。优惠券通常有固定的有效期,过期后需要自动失效。传统的优惠券过期处理方式有:

  1. 定时任务扫描:定期扫描数据库,将过期优惠券标记为失效
  2. 使用时判断:用户使用优惠券时判断是否过期
  3. 缓存过期:将优惠券信息存入Redis等缓存,设置过期时间

这些方式各有优缺点,但都不够实时或资源消耗较大。使用DelayQueue可以实现内存级的优惠券过期管理,既保证实时性,又减少系统资源消耗。

1.2.2 动态失效时间控制

优惠券的一个特点是失效时间可能是动态的:

  • 固定日期失效:如"2023-12-31 23:59:59"
  • 相对时间失效:如"领取后7天内有效"
  • 活动结束失效:如"双11活动结束后失效"

DelayQueue可以很好地支持这些动态失效时间控制,只需在创建延时任务时计算正确的延迟时间即可。

1.2.3 并发安全处理

优惠券系统面临的并发场景主要有:

  • 大量用户同时领取优惠券
  • 用户使用优惠券的同时,优惠券可能正好过期
  • 系统需要动态调整优惠券的有效期

这些场景需要保证数据一致性和操作的原子性。DelayQueue本身是线程安全的,但与数据库操作的配合需要特别注意事务和锁的使用。

1.2.4 实现代码与优化
import java.util.*;
import java.util.concurrent.*;

/**
 * 基于DelayQueue实现的限时优惠券管理系统
 */
public class CouponExpirationManager {
   
    // 延迟队列,用于处理优惠券过期
    private final DelayQueue<CouponExpireTask> delayQueue = new DelayQueue<>();
    
    // 用于存储优惠券ID与任务的映射关系
    private final Map<String, CouponExpireTask> taskMap = new ConcurrentHashMap<>();
    
    // 优惠券服务,实际业务中通过依赖注入获取
    private final CouponService couponService;
    
    // 线程池,用于处理过期优惠券
    private final ExecutorService executorService;
    
    public CouponExpirationManager(CouponService couponService) {
   
        this.couponService = couponService;
        // 创建线程池
        this.executorService = Executors.newFixedThreadPool(
            Runtime.getRuntime().availableProcessors(),
            r -> {
   
                Thread thread = new Thread(r, "coupon-expiration-thread");
                thread.setDaemon(true); // 设置为守护线程
                return thread;
            }
        );
        
        // 启动处理线程
        this.executorService.execute(this::processExpiredCoupons);
    }
    
    /**
     * 添加优惠券过期任务
     * @param couponId 优惠券ID
     * @param expireTime 过期时间点(时间戳)
     */
    public void addCouponExpireTask(String couponId, long expireTime) {
   
        // 计算延迟时间
        long delay = expireTime - System.currentTimeMillis();
        if (delay <= 0) {
   
            // 已过期,直接处理
            couponService.expireCoupon(couponId);
            return;
        }
        
        // 创建过期任务
        CouponExpireTask task = new CouponExpireTask(couponId, delay);
        // 添加到延迟队列
        delayQueue.offer(task);
        // 保存映射关系
        taskMap.put(couponId, task);
        
        System.out.println("优惠券[" + couponId + "]加入过期队列,将在" 
                + new Date(expireTime) + "过期");
    }
    
    /**
     * 更新优惠券过期时间
     * @param couponId 优惠券ID
     * @param newExpireTime 新的过期时间点(时间戳)
     */
    public void updateCouponExpireTime(String couponId, long newExpireTime) {
   
        // 移除旧任务
        CouponExpireTask oldTask = taskMap.remove(couponId);
        if (oldTask != null) {
   
            delayQueue.remove(oldTask);
        }
        
        // 添加新任务
        addCouponExpireTask(couponId, newExpireTime);
        System.out.println("优惠券[" + couponId + "]过期时间已更新为" + new Date(newExpireTime));
    }
    
    /**
     * 取消优惠券过期任务(优惠券被使用或手动作废)
     * @param couponId 优惠券ID
     */
    public void cancelExpireTask(String couponId) {
   
        CouponExpireTask task = taskMap.remove(couponId);
        if (task != null) {
   
            delayQueue.remove(task);
            System.out.println("优惠券[" + couponId + "]过期任务已取消");
        }
    }
    
    /**
     * 处理过期优惠券的线程任务
     */
    private void processExpiredCoupons() {
   
        System.out.println("优惠券过期处理线程已启动");
        while (!Thread.currentThread().isInterrupted()) {
   
            try {
   
                // 批量处理过期优惠券,提高效率
                List<CouponExpireTask> expiredTasks = new ArrayList<>();
                CouponExpireTask task = delayQueue.take(); // 获取一个过期任务
                expiredTasks.add(task);
                
                // 尝试一次性获取多个过期任务
                delayQueue.drainTo(expiredTasks, 100);
                
                // 批量处理过期优惠券
                List<String> couponIds = new ArrayList<>(expiredTasks.size());
                for (CouponExpireTask expiredTask : expiredTasks) {
   
                    String couponId = expiredTask.getCouponId();
                    taskMap.remove(couponId);
                    couponIds.add(couponId);
                }
                
                // 批量更新数据库
                couponService.batchExpireCoupons(couponIds);
                
                System.out.println("已处理" + couponIds.size() + "张过期优惠券");
            } catch (InterruptedException e) {
   
                Thread.currentThread().interrupt();
                break;
            } catch (Exception e) {
   
                System.err.println("处理过期优惠券异常:" + e.getMessage());
            }
        }
    }
    
    /**
     * 关闭管理器
     */
    public void shutdown() {
   
        executorService.shutdown();
        try {
   
            if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) {
   
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
   
            executorService.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
    
    /**
     * 优惠券过期任务
     */
    static class CouponExpireTask implements Delayed {
   
        private final String couponId;
        private final long expireTime; // 过期时间点,单位:毫秒
        
        public CouponExpireTask(String couponId, long delayTime) {
   
            this.couponId = couponId;
            this.expireTime = System.currentTimeMillis() + delayTime;
        }
        
        public String getCouponId() {
   
            return couponId;
        }
        
        @Override
        public long getDelay(TimeUnit unit) {
   
            return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }
        
        @Override
        public int compareTo(Delayed o) {
   
            return Long.compare(this.expireTime, ((CouponExpireTask) o).expireTime);
        }
        
        @Override
        public boolean equals(Object obj) {
   
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            CouponExpireTask that = (CouponExpireTask) obj;
            return couponId.equals(that.couponId);
        }
        
        @Override
        public int hashCode() {
   
            return couponId.hashCode();
        }
    }
    
    /**
     * 优惠券服务接口
     */
    interface CouponService {
   
        void expireCoupon(String couponId);
        void batchExpireCoupons(List<String> couponIds);
    }
    
    /**
     * 测试代码
     */
    public static void main(String[] args) throws InterruptedException {
   
        // 模拟优惠券服务实现
        CouponService couponService = new CouponService() {
   
            @Override
            public void expireCoupon(String couponId) {
   
                System.out.println("优惠券[" + couponId + "]已过期");
            }
            
            @Override
            public void batchExpireCoupons(List<String> couponIds) {
   
                System.out.println("批量处理过期优惠券:" + couponIds);
            }
        };
        
        // 创建优惠券过期管理器
        CouponExpirationManager manager = new CouponExpirationManager(couponService);
        
        // 模拟添加优惠券过期任务
        Calendar calendar = Calendar.getInstance();
        
        // 优惠券1:5秒后过期
        calendar.add(Calendar.SECOND, 5);
        manager.addCouponExpireTask("COUPON_001", calendar.getTimeInMillis());
        
        // 优惠券2:10秒后过期
        calendar.add(Calendar.SECOND, 5);
        manager.addCouponExpireTask("COUPON_002", calendar.getTimeInMillis());
        
        // 优惠券3:15秒后过期
        calendar.add(Calendar.SECOND, 5);
        manager.addCouponExpireTask("COUPON_003", calendar.getTimeInMillis())
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值