LinkedBlockingQueue笔记

LinkedBlockingQueue笔记

概述

LinkedBlockingQueue 是一个利用独占锁(ReentrantLock)实现的阻塞队列,其底层是一个单向链表(静态内部类 Node),默认构造器中,其队列的长度为 int 的最大值,如果队列满了,后续插入节点阻塞,并视情况是否丢弃,该队列有两个独占锁,对头节点操作使用一个独占锁,对尾节点操作使用一个独占锁,来存放被阻塞的线程

/** Current number of elements */
private final AtomicInteger count = new AtomicInteger();

/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();

/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();

/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();

/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();

方法解析

put(E e)

在队列的尾部插入一个节点,如果队列未满,直接插入;如果队列已满,则阻塞当前线程直至队列有空间,如果等待期间被其他线程设置了中断标志,则抛出异常并返回,该方法无返回值

public void put(E e) throws InterruptedException {
    // 队列中不允许插入 null
    if (e == null) throw new NullPointerException();
    // 方法中一般都是预先初始化值为负数
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    // 中断后会抛异常返回
    putLock.lockInterruptibly();
    try {
        // 如果队列已经满了,就放入 notFull 等待队列中阻塞挂起,等待有空间再唤醒
        while (count.get() == capacity) {
            notFull.await();
        }
        // 将该节点设置为尾节点
        enqueue(node);
        // 获取之前队列空间的节点数量并将其原子性的 +1
        c = count.getAndIncrement();
        // 如果插入后还有空间,则唤醒其他插入阻塞的线程
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    // 如果之前队列没有节点,则唤醒读取线程
    if (c == 0)
        // 唤起被 notEmpty 阻塞的线程
        signalNotEmpty();
}

offer(E e)

put(E e) 方法类似,队列有空间时,简单加入到队列尾部,并返回 true;只是在队列满的时候,直接丢弃该节点,并返回 false

public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    final AtomicInteger count = this.count;
    // 队列满了的话直接返回 false
    if (count.get() == capacity)
        return false;
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        // 再次确认队列是否是满的,避免在获取锁之前有线程已经插入一个节点
        if (count.get() < capacity) {
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        }
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
    return c >= 0;
}

offer(E e, long timeout, TimeUnit unit)

offer(E e) 方法类似,只是如果队列满了不是立即丢弃该节点,而是等待一段时间后如果队列还是满的就直接丢弃,传入 <= 0 的值时,与不带时间参数的方法一直

public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    if (e == null) throw new NullPointerException();
    // 将时间切换为纳秒单位
    long nanos = unit.toNanos(timeout);
    int c = -1;
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        while (count.get() == capacity) {
            // 如果纳秒数为小于等于 0 的值,就直接丢弃
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        enqueue(new Node<E>(e));
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
    return true;
}

take()

取出队列中第一个节点,如果队列是空的,则阻塞挂起,等待节点入队,如果等待被唤起过程中被中止了,并抛出异常

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;
}

poll()

取出队列中第一个节点,如果队列是空的,则直接返回 false

public E poll() {
    final AtomicInteger count = this.count;
    if (count.get() == 0)
        return null;
    E x = null;
    int c = -1;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        if (count.get() > 0) {
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        }
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

poll(long timeout, TimeUnit unit)

poll() 方法类似,只是如果队列是空不直接返回 null ,而是等待一段时间后如果队列还是空的就直接返回 null,传入 <= 0 的值时,与不带时间参数的方法一直

peek()

获取头部节点,但是不移除它,如果队列是空的,则直接返回 null,此方法不会被阻塞

public E peek() {
    // 队列是空,直接返回 null
    if (count.get() == 0)
        return null;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        // 获取头部节点,head 是哨兵节点
        Node<E> first = head.next;
        // 避免在拿到锁时,队列成空,造成空指针异常
        if (first == null)
            return null;
        else
            // 返回头部节点的值
            return first.item;
    } finally {
        takeLock.unlock();
    }
}

remove(Object o)

移除一个指定的元素,如果有该节点,则删除该节点并返回 true,否则返回 false

public boolean remove(Object o) {
    // 不能插入 null 值,所以也不会存在 null
    if (o == null) return false;
    // 双重锁加锁
    fullyLock();
    try {
        // 遍历队列直至找到该值
        for (Node<E> trail = head, p = trail.next;
             p != null;
             trail = p, p = p.next) {
            if (o.equals(p.item)) {
                // 从队列中移除
                unlink(p, trail);
                return true;
            }
        }
        return false;
    } finally {
        // 释放双重锁,并且顺序与加锁顺序相反
        fullyUnlock();
    }
}

猜你喜欢

转载自blog.csdn.net/ycxzuoxin/article/details/89284574