JDK17 阻塞队列 LinkedBlockingQueue

阻塞队列

阻塞队列与普通队列的区别:

  1. 阻塞添加元素。队列已满时阻塞添加元素的线程,直到队列不满才唤醒。
  2. 阻塞删除元素。队列为空时阻塞取出元素的线程,直到队列不空才唤醒。

同样是阻塞队列,LinkedBlockingQueue与ArrayBlockingQueue的区别是:

LinkedBlockingQueueArrayBlockingQueue
容器单向链表数组
队列容量默认是Integer.MAX_VALUE,无界队列初始化必须指定大小,有界队列
添加与移出用一把锁添加与移出用2把锁,效率更高

LinkedBlockingQueue

LinkedBlockingQueuecapacity参数表示队列容量。默认是Integer.MAX_VALUE,因此被称为无界队列,有OOM的风险。

构造器方法含义
LinkedBlockingQueue()无界队列
LinkedBlockingQueue(int capacity)指定容量,有界队列
LinkedBlockingQueue(Collection<? extends E> c)c集合元素添加进无界队列
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
   if (capacity <= 0) throw new IllegalArgumentException(); // 参数校验
    this.capacity = capacity;
    last = head = new Node<E>(null); // head指向哑节点
}

Node<E>节点是队列的内部类,目的是以单向链表(只有next节点没有prev节点)封装元素。
lasthead是队列的首尾节点。

static class Node<E> {
        E item;
        Node<E> next;
        Node(E x) { item = x; }
    }
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();

添加元素的线程称为生产者,取出元素的线程称为消费者
生产者先获取putLock 独占锁才能添加元素,消费者先获取takeLock 独占锁才能取出元素。
元素满时,生产者调用notFull.await(),挂起自己,释放putLock 锁,直到消费者调用notFull.signal()唤醒某个生产者,那个生产者重新获取锁后从notFull.await()方法继续执行。
元素空时,消费者调用notEmpty .await(),挂起自己,释放takeLock 锁,直到生产者者调用notEmpty .signal()唤醒某个消费者,那个消费者重新获取锁后从notEmpty .await()方法继续执行。
生产者和消费者平时是互不干扰的,直到队列为空或者队列为满时才相互交互。

添加元素

方法含义
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException阻塞特定时间将元素放入队列尾部
boolean offer(E e)非阻塞地将元素放入队列尾部
void put(E e) throws InterruptedException阻塞地将元素放入队列尾部
public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    if (e == null) throw new NullPointerException(); // 参数校验
    long nanos = unit.toNanos(timeout);
    final int c;
    final ReentrantLock putLock = this.putLock; // 独占锁,确保同一时刻只有1个线程可以添加元素
    final AtomicInteger count = this.count; // 当前元素数量
    putLock.lockInterruptibly(); // 获取锁,可响应中断
    // 运行至此,同一时刻只有当前线程可以往下执行
    try {
        while (count.get() == capacity) { // 队列已满
            if (nanos <= 0L)
                return false; // 超时,且添加元素失败,返回false
            nanos = notFull.awaitNanos(nanos); // 当前线程让出锁,等待一段时间
            // 运行至此,从等待状态恢复,并且重新获取`putLock`锁,继续判断`while`循环
        }
        enqueue(new Node<E>(e)); // 加入队列
        c = count.getAndIncrement(); // getAndIncrement(),返回的是increment之前的值
        if (c + 1 < capacity) // 如果队列未满,避免有生产者在等待,唤醒一个生产者
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    // c是当前线程获取`putLock`独占锁时的队列元素数量
    // 如果c=0,代表此时队列为空,可能有很多消费者线程阻塞在`notEmpty.await()`
    // 运行至此,起码当前线程往队列添加1个元素,因此唤醒生产者线程
    if (c == 0)
        signalNotEmpty();
    return true;
}

private void signalNotEmpty() {
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
}
// 是线程安全的,因为外层方法加锁了
private void enqueue(Node<E> node) {
   // assert putLock.isHeldByCurrentThread();
    // assert last.next == null;
    last = last.next = node;
}

取出元素

方法含义
boolean remove(Object o)删除指定元素,判断元素相等的方法是o.equlas()方法
E take()阻塞地取出队列首部元素
E poll()非阻塞地取出队列首部元素
E poll(long timeout, TimeUnit unit) throws InterruptedException阻塞特定时间内取出队列首部元素
E peek()查看队列首部元素
public boolean remove(Object o) {
    if (o == null) return false;
    fullyLock(); // 同时获取添加锁和取出锁
    try {
        for (Node<E> pred = head, p = pred.next;
             p != null;
             pred = p, p = p.next) { // 遍历元素删除对应节点
            if (o.equals(p.item)) {
                unlink(p, pred); // 从链表中删除节点
                return true; 
            }
        }
        return false; // 未找到对应元素,返回false
    } finally {
        fullyUnlock();
    }
}
// 同时获取添加锁和取出锁
void fullyLock() {
    putLock.lock();
    takeLock.lock();
}
// 同时释放添加锁和取出锁
void fullyUnlock() {
    takeLock.unlock();
    putLock.unlock();
}
// 没有 `throws InterruptedException`,不响应中断
public E poll() {
   final AtomicInteger count = this.count;
    if (count.get() == 0)
        return null; // 非阻塞,而不是`notEmpty.await();`
    final E x;
    final int c;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        if (count.get() == 0)
            return null; // 非阻塞,而不是`notEmpty.await();`
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1) // 仍然可以取出元素, 因此唤醒下一个生产者
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    // 与取出元素类似,c是当前线程获取`takeLock`独占锁时的队列元素数量
    // 如果c=capacity,代表此时队列为满,可能有很多生产者线程无法添加元素,阻塞在`notFull.await()`
    // 当前线程执行至此,队列不满,因此唤醒一个生产者线程
    if (c == capacity)
        signalNotFull();
    return x;
}

// 取出元素
private E dequeue() {
    Node<E> h = head; // head是哑结点
    Node<E> first = h.next; // first是第一个存储元素的节点
    h.next = h; // help GC // 让当前节点没有引用别的节点,有助于垃圾回收识别
    head = first; // 舍弃当前head节点,first节点成为新的head节点
    E x = first.item;
    first.item = null;
    return x;
}
public E peek() {
   final AtomicInteger count = this.count;
    if (count.get() == 0)
        return null; // 如果队列没有元素,返回null
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock(); // 为避免线程安全问题,获取取锁
    try {
        return (count.get() > 0) ? head.next.item : null;
    } finally {
        takeLock.unlock();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值