BlockingQueue 分批处理数据

BlockingQueue 分批处理数据


BlockingQueue 简介 :

​ BlockingQueue 是 jdk-1.5 中新增的并发包(concurrent)中的阻塞队列接口,主要用来实现高并发场景下的数据传输问题。其常见实现类主要有: ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue。队列定义时可设置队列长度,默认长度为 Integer.MAX_VALUE。

// 其核心方法如下
public interface BlockingQueue<E> extends Queue<E> {
    
    // 将给定元素设置到队列中,如果设置成返回 true,否则返回 false,如果为限长队列,推荐使用 offer 方法
    boolean add(E e);
    
    // 将给定元素设置到队列中,如果设置成功返回 true,否则返回 false,元素不能为空,否则抛出空指针异常
    boolean offer(E e);
    
    // 将给定元素设置到队列中,如果队列已满,则该方法一直阻塞,直到队列有多余空间
    void put(E e) throws InterrutedException;
    
    // 将给定元素在给定时间内设置到队列中,如果设置成功返回 true,否则返回 false
    boolean offer(E e, TimeUnit unit) throws InterrutedException;
    
    // 从队列中获取值,若队列中没有值,则该方法一直阻塞,直到有值且成功获取到值
    E take() throws InterrutedException;
    
    // 在给定时间内从队列中获取值,时间到了直接调用普通的 poll 方法,为 null 则返回 null
    E poll(long timeout, TimeUnit unit) throws InterrutedException;
    
    int remainingCapacity();   // 获取队列剩余空间
    
    boolean remove(E e);   // 从队列中移除指定的值
    
    boolean contains(E e);   // 判断队列中是否包含该值
    
    int drainTo(Collection<? super E> c);   // 将队列中的值全部移除,并发设置到指定的集合中
    
    // 将队列中指定数量的值全部移除,并发设置到指定集合中
    int drainTo(Collection<? super E> c, int maxElements);
    
}

主要实现类 :

  • ArrayBlockingQueue : 基于数组实现的阻塞队列,在其内部维护了一个定长数组和两个整型变量,定长数组用来缓存队列中的数据对象,整型变量分别标识队列的头部和尾部在数组中的位置。ArrayBlockingQueue 在生产者放入数据和消费者消费数据时,都是共用同一个锁,这也意味着两者无法真正并行运行,这点尤其不同于 LinkedBlockingQueue。ArrayBlockingQueue 在插入或删除元素时不会产生或销毁额外的对象实例,而 LinkedBlockingQueue 会产生一个 Node 对象,这在长时间内需要高效并发的处理大批量数据的系统中,其对 GC 的影响还是挺大的。另外,在创建 ArrayBlockingQueue 时,我们可以控制对象内部是否采用公平锁,默认采用非公平锁。
  • LinkedBlockingQueue : 基于链表实现的阻塞队列,在其内部维护了一个由链表构成的数据缓冲队列,当生产者往队列中放入数据时,队列会先从生产者手中获取数据,然后缓存在队列内部,而生产者则立刻返回;当队列缓冲区的数据量达到最大缓存值时会阻塞生产者,直到消费者消费掉部分数据使队列不再满值时才会唤醒生产者线程。LinkedBlockingQueue 之所以能够高效的处理并发数据是因为生产者端和消费者端分别采用了独立的锁来控制数据同步,这也就意味着再高并发场景下两者可以并行的操作队列中的数据,以此来提高整个队列的并发性能。在创建 LinkedBlockingQueue 时,如果没有指定队列大小,则默认大小为 Integer.MAX_VALUE,这样的话,如果生产者的生产速度大于消费者的消费速度,会严重消耗系统内存。
  • PriorityBlockingQueue : 基于优先级实现的阻塞队列,优先级的判断是通过构造函数传入的 Compator 来决定的,不同于 LinkedBlockingQueue 的是,PriorityBlockingQueue 不会阻塞生产者线程,而会阻塞消费者线程,当队列中没有可消费的数据时会阻塞消费者线程。PriorityBlockingQueue 内部控制线程同步使用的是公平锁。

LinkedBlockingQueue 分批处理数据 :

​ 大概逻辑就是请求来了之后将待入库的数据放入队列中,对列中的数据在子线程中分批进行入库操作。

// 分批入库线程类
public class BatchThread extends Thread {
    private static final Logger logger = LogManager.getLogger(BatchThread.class);
    private static BatchThread batchThread;
	// 定义一个长度为 10 的阻塞队列
    private final LinkedBlockingQueue<List<TestPo>> blockingQueue = new LinkedBlockingQueue<>(10);
    private TestService testService;   // 注入入库 service
    private volatile boolean running = true;   // 线程运行状态
    private BatchThread() {};
    
    public TestService getTestService() {
        return testService;
    }
    public void setTestService(TestService testService) {
        this.testService = testService;
    }

    @Override
    public void run() {
        while (running) {
            try {   // 从队列中获取要入库的数据(即一个节点的数据)
                List<TestPo> list = blockingQueue.take();   // 调用队列接口类中 take 方法
                if (list.isEmpty()) {
                    return;
                }
                // 获取到队列中某个节点的数据列表后,若列表太长,可考虑将此列表分批处理
                // 如 每次只取 100条(可参考度娘)
                // 调用 service 方法进行入库
                boolean res = testService.insertBatch(list);
                if (!res) {
                    logger.error("队列数据入库失败 list = {}", list);
                }
                logger.info("list = {}", list);
                Thread.sleep(2000);   // 子线程睡眠时间由数据库性能而定
            } catch (InterruptedException e) {
                e.printStackTrace();
                logger.error("队列数据入库任务执行失败");
            }
        }
    }

    /**
     * 获取单例线程实例(双检查锁模式)
     * @return
     */
    public static BatchThread getInstance() {
        if (batchThread == null) {
            synchronized (BatchThread.class) {
                if (batchThread == null) {
                    batchThread = new BatchThread();
                }
            }
        }
        return batchThread;
    }

    /**
     * 将待入库数据放入队列
     * @param list
     */
    public void putQueue(List<TestPo> list) {
        if (list == null || list.isEmpty()) {
            logger.error("放入队列数据不能为空 list = {}", list);
            return;
        }
        try {
            blockingQueue.put(list);   // 调用队列接口类中的 put 方法
        } catch (InterruptedException e) {
            e.printStackTrace();
            logger.error("数据入列失败 list = {}", list);
        }
    }
}

// 测试 controller
@RestController
@RequestMapping("/common/test")
public class TestController {

    private final TestService testService;
    public TestController(TestService testService) {
        this.testService = testService;
    }

    @RequestMapping("/insertBatch")
    public APIResponse<Map<String, Object>> insertBatch(@RequestBody TestPo testPo) {
        List<TestPo> list = new ArrayList<>();
        TestPo testPo1;
        for (int i = 0; i < 1; i++) {
            testPo1 = new TestPo();
            testPo1.setUserName("XGLLHZ" + testPo.getAge());
            testPo1.setAge(23);
            testPo1.setGender(2);
            list.add(testPo1);
        }
		// 获取线程实例
        BatchThread batchThread = BatchThread.getInstance();

        try {   // 将待入库数据放入队列中
            batchThread.putQueue(list);
			// 判断线程实例的依赖实例 TestService 是否存在,若不存在则设置,
			// 以访子线程启动报错
            if (batchThread.getTestService() == null) {
                batchThread.setTestService(testService);
            }
			// 若子线程未启动,则启动子线程(一般情况下是第一次请求时)
            if (!batchThread.isAlive()) {
                batchThread.start();
            }
        } catch (Exception e) {
            e.printStackTrace();
            return new APIResponse<>("100", "批量插入失败");
        }
        return new APIResponse<>();
    }
}

@XGLLHZ-飞鸟和蝉.mp3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值