JUC——BlockingQueue


BlockingQueue阻塞队列,它的阻塞体现在两个方面:

  1. 当队列为空时,出队操作将被阻塞;
  2. 当队列已经满的时候,进行入队将被阻塞;

BlockingQueue它继承自Queue接口,核心方法主要包括:

抛异常boolean结果阻塞线程阻塞/超时
入队add(o)offer(o)put(o)offer(o, timeout, timeunit)
出队remove(o)poll()take()poll(timeout, timeunit)
获取元素(不出队)element()peek()

上面四种元素操作分类都是在需要阻塞时,处置结果的不同:

抛异常:如果在入队时需要阻塞或者出队时需要阻塞的话,将会抛出:IllegalStateException异常;
boolean结果:如果操作需要需要阻塞的话,则直接返回false;
阻塞:如果入队和出队需要阻塞,则挂起当前线程;
阻塞/超时:在阻塞的基础上,允许添加阻塞的时间,超过阻塞时间,则返回false

BlockingQueue的实现类:

ArrayBlockingQueue:有界阻塞队列,内部基于数组实现;
LinkedBlockingQueue:
DelayQueue
PriorityBlockingQueue
SynchronousQueue

1. ArrayBlockingQueue

ArrayBlockingQueue继承自AbstractQueue并实现了BlockingQueue接口,它在创建的时候必须传入数组的长度,而不像ArrayList一样有自己的扩容机制;并且队列长度一但创建不允许再修改;
ArrayBlockingQueue依赖于ReentrantLock,所以它也提供公平和非公平的策略,默认是非公平模式;

1.1 属性和构造器
// 存放元素的数组
final Object[] items;

// 下一个出队的位置
int takeIndex;

// 下一个入队的位置
int putIndex;

// 当前队列中有多少元素
int count;

final ReentrantLock lock;
// 非空条件队列,如果队列为空时进行出队操作,则使用它进行阻塞
private final Condition notEmpty;
// 非满条件队列,如果队列满了继续入队时,则使用它进行阻塞
private final Condition notFull;
// 循环队列元素
transient Itrs itrs = null;


// 传入阻塞队列的长度,false是指公平和非公平模式
public ArrayBlockingQueue(int capacity) {
    this(capacity, false);
}

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

// 创建时,直接传递进来一个集合,将期添加到到阻塞队列中
public ArrayBlockingQueue(int capacity, boolean fair,
                          Collection<? extends E> c) {
    this(capacity, fair);

    final ReentrantLock lock = this.lock;
    lock.lock(); // Lock only for visibility, not mutual exclusion
    try {
        int i = 0;
        try {
            for (E e : c) {
                checkNotNull(e);
                items[i++] = e;
            }
        } catch (ArrayIndexOutOfBoundsException ex) {
            throw new IllegalArgumentException();
        }
        count = i;
		// i++是后置+1,所以putIndex就是下一个要插入的位置
		// 如果队列满了,则从头开始,因为头部元素可能出队,那么再入队时,需要判断count如果等待列队总长度,则说明队列满了
        putIndex = (i == capacity) ? 0 : i;
    } finally {
        lock.unlock();
    }
}
1.2 put & take
public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    // 加可中断锁,因为ArrayBlockingQueue是线程安全的,所以会加锁
    lock.lockInterruptibly();
    try {
        // 这里如果相等,表示队列已满,则使用notFull进行wait
        while (count == items.length)
            notFull.await();
        // 否则,加入队列尾部
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

private void enqueue(E x) {
    final Object[] items = this.items;
    items[putIndex] = x;
    // 如果putIndex == items.length说明putIndex到了队列最尾部,则将putIndex=0,可以循环使用队列
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    // 添加进来了元素,则通知因获取元素而阻塞的线程
    notEmpty.signal();
}

从源码中可以看出,它是一种阻塞式的put,如果要入队并且队列已满,则进行阻塞等待;

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 要出队,如果队列中没有元素,则通过notEmpty条件队列进行阻塞
        // 这里是个while循环,为了防止被唤醒之后,资源又被别的线程抢走,会再次进行等待
        while (count == 0)
            notEmpty.await();
        // 否则,出队
        return dequeue();
    } finally {
        lock.unlock();
    }
}
private E dequeue() {
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    E x = (E) items[takeIndex];
    // takeIndex记录要出队元素的位置,将要出队的元素位置设置为null
    items[takeIndex] = null;
    // 如果takeIndex == items.length,则将takeIndex设置为0
    if (++takeIndex == items.length)
        takeIndex = 0;
    // 元素数据减一
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    // 通过因队列满,而无法入队的线程进行入队操作
    notFull.signal();
    return x;
}

对put和take有了了解之后,再看剩余的会非常轻松,具体代码不在这里列了;

2. LinkedBlockingQueue

LinkedBlockingQueue也是阻塞队列的一种实现,它是基于单向链表存储节点数据,它继承自AbstractQueue实现了BlockingQueue接口,所以上面提到的一些方法它都包含;
LinkedBlockingQueue在创建的时候,它需要传入链表的长度,如果不指定长度,则默认长度为Integer的最大值;
LinkedBlockingQueue采用两把锁,一把控制入队的putLock,一把控制出队的takeLock,入队和出队可以并发;

2.1 成员变量和构造方法
// 链表的总容量
private final int capacity;

// 当前容器中的元素个数
private final AtomicInteger count = new AtomicInteger();

// 头节点
transient Node<E> head;

// 尾节点
private transient Node<E> last;

// 出队锁控制
private final ReentrantLock takeLock = new ReentrantLock();

// 队列为空时,出队则阻塞
private final Condition notEmpty = takeLock.newCondition();

// 入队锁控制
private final ReentrantLock putLock = new ReentrantLock();

// 队列满时,继续put则阻塞
private final Condition notFull = putLock.newCondition();

// 不指定长度默认Integer最大值
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    // 初始化时,默认head和last指向一个空节点
    last = head = new Node<E>(null);
}

public LinkedBlockingQueue(Collection<? extends E> c) {
    // 队列长度 默认Integer最大值
    this(Integer.MAX_VALUE);

    // 向链表中添加元素使用putLock
    final ReentrantLock putLock = this.putLock;
    putLock.lock(); // Never contended, but necessary for visibility
    try {
        int n = 0;
        for (E e : c) {
            if (e == null)
                throw new NullPointerException();
            // 队列满了则报错
            if (n == capacity)
                throw new IllegalStateException("Queue full");

            enqueue(new Node<E>(e));
            ++n;
        }
        count.set(n);
    } finally {
        putLock.unlock();
    }
}

// 向队列尾部添加节点
private void enqueue(Node<E> node) {
    last = last.next = node;
}
2.2 put & take
public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    int c = -1;
    // 将元素封装成Node
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        // 如果队列已满,则wait
        while (count.get() == capacity) {
            notFull.await();
        }
        // 否则,入队
        enqueue(node);
        // 如果队列未满,则唤醒因队列满而无法入队的线程
        // getAndIncrement先获取未自增之前的值
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    // 如果c=0,则有可能存在出队操作被阻塞,而已添加入队元素,通过唤醒
    if (c == 0)
        signalNotEmpty();
}
static class Node<E> {
    E item;

    Node<E> next;

    Node(E x) { item = x; }
}
private void enqueue(Node<E> node) {
    last = last.next = node;
}
private void signalNotEmpty() {
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        // 如果c=0,表示队列为空了,唤醒
        notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
}

由于LinkedBlockingQueue采用两把锁来控制队头和队尾,所以添加元素之后,需要分别通知这两把阻塞锁;

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        // 没有可出队的元素,则阻塞
        while (count.get() == 0) {
            notEmpty.await();
        }
        // 出队
        x = dequeue();
        c = count.getAndDecrement();// 可继续出队
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    // 如果队列已经满了,并且本次已经出队一个元素,有一个空余位置,则唤醒入队线程
    if (c == capacity)
        signalNotFull();
    return x;
}
private E dequeue() {
    Node<E> h = head;
    Node<E> first = h.next;
    h.next = h; // help GC
    head = first;
    E x = first.item;
    first.item = null;
    return x;
}
private void signalNotFull() {
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        notFull.signal();
    } finally {
        putLock.unlock();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值