并发队列-LinkedBlockingQueue分析

  • LinkedBlockingQueue含有一个链表+2把锁+2个条件
  • LinkedBlockingQueue的属性和Node节点内部类:
/**
     * Linked list node class
     */
    static class Node<E> {
        E item;

        /**
         * One of:
         * - the real successor Node
         * - this Node, meaning the successor is head.next
         * - null, meaning there is no successor (this is the last node)
         */
        Node<E> next;

        Node(E x) { item = x; }
    }

    /** The capacity bound, or Integer.MAX_VALUE if none 
    链表容量,最大值 Integer.MAX_VALUE */
    private final int capacity;

    /** Current number of elements 当前元素个数 */
    private final AtomicInteger count = new AtomicInteger();

    /**
     * Head of linked list.
     * Invariant: head.item == null
     * 头部节点
     */
    transient Node<E> head;

    /**
     * Tail of linked list.
     * Invariant: last.next == null
     * 尾部节点
     */
    private transient Node<E> last;

    /** 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();
  • LinkedBlockingQueue重要知识点:
    • 创建
    • 入队(添加元素)
    • 出队(删除元素)
  • LinkedBlockingQueue使用方法 (创建):
// 不指定链表容量 默认Integer.MAX_VALUE  可认为是无界队列
LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
对应源码:
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}
// 指定链表容量
LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);
对应源码:
public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    last = head = new Node<E>(null);
}

LinkedBlockingQueue入队方法:

  • public boolean offer(E e)
    原理: 在队尾插入一个元素, 如果队列没满,立即返回true; 如果队列满了,立即返回false
    对应代码:
   public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        final AtomicInteger count = this.count; // 获取队列元素个数
        if (count.get() == capacity) // 队列满了返回false
            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(); // 获取旧值并把count + 1
                if (c + 1 < capacity)  // 比较添加元素后的容量,如果小于总容量,说明还可以添加至少一个元素
                    notFull.signal(); // 唤醒等待notFull条件的其中一个线程
            }
        } finally {
            putLock.unlock();
        }
        if (c == 0) // 第一次添加的时候 c 就是count的值 0  
            signalNotEmpty(); // 通知等待notEmpty条件的线程
        return c >= 0;
    }
   private void enqueue(Node<E> node) {
        // assert putLock.isHeldByCurrentThread();
        // assert last.next == null;
        last = last.next = node;
    }
   private void signalNotEmpty() {
         final ReentrantLock takeLock = this.takeLock;
        takeLock.lock(); // 获取出队锁
        try {
            notEmpty.signal(); // 唤醒等待notEmpty条件的其中一个线程
        } finally {
            takeLock.unlock();
        }
    }
  • public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException
    原理:在队尾插入一个元素,,如果队列已满,则进入等待,直到出现以下三种情况:
    • 被唤醒
    • 等待时间超时
    • 当前线程被中断
      对应代码:
   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) { // 容量已满
                if (nanos <= 0) // 已经超时
                    return false;
                /*
                 * 进行等待: 在这个过程中可能发生三件事: 
                 * 1、被唤醒-->继续当前这个while循环
                 * 2、超时-->继续当前这个while循环 返回false
                 * 3、被中断-->抛出中断异常InterruptedException
                 */
                nanos = notFull.awaitNanos(nanos);
            }
            enqueue(new Node<E>(e)); // 入队
            c = count.getAndIncrement();
            if (c + 1 < capacity)  // 队列没满 
                notFull.signal();  // 唤醒等到notFull条件的其中一个线程
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        return true;
    }
  • public void put(E e) throws InterruptedException
    原理: 在队尾插入一个元素,如果队列满了,就一直阻塞,直到队列不满了(notFull通知)或者线程被中断
   public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        // Note: convention in all put/take/etc is to preset local var
        // holding count negative to indicate failure unless set.
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            /*
             * Note that count is used in wait guard even though it is
             * not protected by lock. This works because count can
             * only decrease at this point (all other puts are shut
             * out by lock), and we (or some other waiting put) are
             * signalled if it ever changes from capacity. Similarly
             * for all other uses of count in other wait guards.
             */
            while (count.get() == capacity) {
                notFull.await();
            }
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
    }

LinkedBlockingQueue出队方法:

  • public E poll()
    原理: 删除队列头部元素,如果没有元素,直接返回null;如果有元素,返回并出队
   public E poll() {
        final AtomicInteger count = this.count; // 获取元素数量
        if (count.get() == 0) // 没有返回null
            return null;
        E x = null;
        int c = -1;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock(); // 获取出队锁
        try {
            if (count.get() > 0) { // 元素不为0
                x = dequeue(); // 出队
                c = count.getAndDecrement();
                if (c > 1)
                    notEmpty.signal(); // 元素个数大于1,即出队之后至少还有一个元素,通知等待noEmpty条件的其中一个线程
            }
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }
   private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        // assert head.item == null;
        Node<E> h = head;  // 获取头节点
        Node<E> first = h.next; // 将头节点的下一个节点赋值给first
        h.next = h; // help GC 将当前将要出队的节点置null(为了使其做head节点做准备)
        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();
        }
    }
  • public E poll(long timeout, TimeUnit unit) throws InterruptedException
    原理: 删除队列头部元素,如果队列不空,出队;如果队列已空且已经超时,返回null;如果队列已空且时间未超时,则进入等待,直到出现以下三种情况:
    • 被唤醒
    • 等待时间超时
    • 当前线程被中断
   public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        E x = null;
        int c = -1;
        long nanos = unit.toNanos(timeout); // 单位转为纳秒
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly(); // 获取出队锁
        try {
            while (count.get() == 0) {  // 队列没有元素
                if (nanos <= 0)  // 超时返回null
                    return null;
                nanos = notEmpty.awaitNanos(nanos); // 等待 直到被唤醒,超时或者被中断
            }
            x = dequeue(); // 出队
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }
  • public E take() throws InterruptedException
    原理: 删除队列头部元素,如果队列空了,一直阻塞,直到队列不为空或者线程被中断
   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;
    }
  • 三种入队对比

    • offer(E e):如果队列没满,立即返回true; 如果队列满了,立即返回false — 不阻塞
    • put(E e):如果队列满了,一直阻塞,直到队列不满了或者线程被中断 -->阻塞
    • offer(E e, long timeout, TimeUnit unit):在队尾插入一个元素,,如果队列已满,则进入等待,直到出现以下三种情况: -->阻塞
      • 被唤醒
      • 等待时间超时
      • 当前线程被中断
  • 三种出队对比

    • poll():如果没有元素,直接返回null;如果有元素,出队 — 不阻塞
    • take():如果队列空了,一直阻塞,直到队列不为空或者线程被中断 — 阻塞
    • poll(long timeout, TimeUnit unit):如果队列不空,出队;如果队列已空且已经超时,返回null;如果队列已空且时间未超时,则进入等待,直到出现以下三种情况: — 阻塞
      • 被唤醒
      • 等待时间超时
      • 当前线程被中断

扩展信息:

  • ArrayBlockingQueue与LinkedBlockingQueue对比
    • ArrayBlockingQueue:
      一个对象数组+一把锁+两个条件
      入队与出队都用同一把锁
      在只有入队高并发或出队高并发的情况下,因为操作数组,且不需要扩容,性能很高
      采用了数组,必须指定大小,即容量有限
    • LinkedBlockingQueue:
      一个单向链表+两把锁+两个条件
      两把锁,一把用于入队,一把用于出队,有效的避免了入队与出队时使用一把锁带来的竞争。
      在入队与出队都高并发的情况下,性能比ArrayBlockingQueue高很多
      采用了链表,最大容量为整数最大值,可看做容量无限
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值