java阻塞队列---BlockingQueue

java阻塞队列—BlockingQueue

阻塞队列解决的问题

  1. 为什么要用阻塞队列?

    在日常编码中, 我们会使用到生产者消费者模式, 使用阻塞队列可以快速的实现这一模式

  2. 线程池中阻塞队列的使用

    在ThreadPoolExecutor中, 当核心线程数给占用完毕后, 后来的任务都会被加入到阻塞队列中.

    这里为什么不用普通队列?

    首先, 线程池的需求是核心线程会一直保持运行, 而worker在执行完毕当前任务之后, 会从队列中获取下一个任务来执行, 如果队列中没有任务, worker需要等待任务的到来并且获取他

    所以阻塞队列的特性完美契合了这一需求

    如果我们使用普通队列的话, 线程的阻塞和唤醒需要我们额外的编写代码来实现, 这无疑增加了并发编程的复杂性

ArrayBlockingQueue

在juc包中有很多类都实现了BlockingQueue接口, 这里我们主要介绍一下ArrayBlockingQueue

与ArrayList不同的是, ArrayBlockingQueue没有无参构造器, 我们必须给该队列确定一个容量, 所以该队列是无法自动扩容的

offer()

阻塞队列也是实现了队列接口的, 所以offer方法在这里也会被实现

    public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        // 获取资源锁, 入队和出队操作都需要获取这个锁
        // 所以可以得知 入队和出队操作在该类中是无法同时进行的
        lock.lock();
        try {
            // 队列如果是满的, 则直接拒绝
            // offer中这里是不阻塞的
            if (count == items.length)
                return false;
            else {
                // 队列没满则进行入队操作
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }

// 带有入参的offer方法, 在队列满的时候, 该方法会根据入参来设置等待时间
// 在时间内如果队列有消费行为, 那么需要入列的数据则有机会入列(并发情况)
    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        checkNotNull(e);
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        // 这里获取的是可被中断的锁
        lock.lockInterruptibly();
        try {
            // 循环等待唤醒
            // 典型的生产者, 被唤醒的时候需要重新判断队列是否满
            while (count == items.length) {
                // 等待时间超出设置时间后, 直接拒绝入队
                if (nanos <= 0)
                    return false;
                // 队列满, 则进行等待阻塞, 并且一定时间内被唤醒
                nanos = notFull.awaitNanos(nanos);
            }
            enqueue(e);
            return true;
        } finally {
            lock.unlock();
        }
    }

enqueue()

private void enqueue(E x) {
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    final Object[] items = this.items;
    items[putIndex] = x;
    // 循环队列的操作, 指针到达数组长度后, 从头开始入队
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    // 入队操作后, 对notEmpty信号量进行唤醒, signal随机唤醒一个await的线程
    // 因为有数据入队, 所以队列中一定不为空, 那么消费者可以消费
    notEmpty.signal();
}

take()

从阻塞队列中获取数据, 如果没有数据则阻塞等待获取, 这里的阻塞不会被时间打破, 只有在线程中断的时候会才会停止阻塞

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 队列为空, 阻塞等待唤醒
        // 入队的时候notEmpty信号量会被signal
        while (count == 0)
            notEmpty.await();
        // 出队操作
        return dequeue();
    } finally {
        lock.unlock();
    }
}

dequeue()

在dequeue中并没有判断队列是否为空, 因为在进入这个方法之前, 都会同步判断count是否为0

只有count不为0的时候才会进入这个方法

    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        // 队列头指针, 获取队列第一个入队的数据
        E x = (E) items[takeIndex];
        // 数组中该位置数据置空
        items[takeIndex] = null;
        // 移动指针, 循环队列指针到达队尾+1则变为0
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        // 消费完毕之后队列非满, 唤醒一个生产者生产数据
        notFull.signal();
        return x;
    }

总结

在ArrayBlockingQueue中, 所有的入队出队操作都是在上锁之后进行的, 所以无需进行并发的操作处理, 同一时间只有一个线程进行入队出队操作

总的来说就是一个数组实现的队列, 加上信号量实现生产者消费者模式, 但是入队出队操作只有一个线程可以进行, 有点浪费性能就是了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值