阻塞队列 LinkedBlockingQueue

LinkedBlockingQueue

基于单向链表实现的阻塞队列
在这里插入图片描述

属性
  	/** The capacity bound, or Integer.MAX_VALUE if none */
  	//队列容量
    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();
构造方法
  	// 无参构造函数,默认容量为Integer.MAX_VALUE
  	public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }


    /**
     * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
     *
     * @param capacity the capacity of this queue
     * @throws IllegalArgumentException if {@code capacity} is not greater
     *         than zero
     */
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        //初始化头、尾节点。值为null
        last = head = new Node<E>(null);
    }
 /**
     * Linked list node class
     */
    static class Node<E> {
    	// Node节点保存的值
        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; }
    }

在这里插入图片描述

重要方法
offer
 public boolean offer(E e) {
 		// 阻塞队列不能传入 null 元素
        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;
    }


元素入队

 /**
   * Links node at end of queue.
   *
   * @param node the node
   */
  private void enqueue(Node<E> node) {
      // assert putLock.isHeldByCurrentThread();
      // assert last.next == null;
      last = last.next = node;
  }

在这里插入图片描述



判断在添加元素之前,队列是否为空。如果为空那么唤醒 Condition队列中被阻塞线程

        if (c == 0)
            signalNotEmpty();

如果 c==0 成立,说明在此添加元素操作之前,队列为空,那么如果有其他线程要移除此队列元素就会被阻塞。向此队列中添加元素过后 队列非空,就需要唤醒被阻塞线程(如果存在此被阻塞线程的话)

   /**
     * Signals a waiting take. Called only from put/offer (which do not
     * otherwise ordinarily lock takeLock.)
     */
    private void signalNotEmpty() {
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
        	// 唤醒被阻塞线程
            notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
    }

signalNotEmpty 方法只能由 put/offer 调用。 add 方法内部调用了offer



put
	/**
     * Inserts the specified element at the tail of this queue, waiting if
     * necessary for space to become available.
     *
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    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();
    }

put 方法和 offer方法基本一致,区别在于队列已满时线程阻塞

 while (count.get() == capacity) {
     notFull.await();
  }
poll
 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)
	        // Signals a waiting put. Called only from take/poll.
            signalNotFull();
        return x;
    }


移除队列元素

	/**
    * Removes a node from head of queue.
    *
    * @return the node
    */
   private E dequeue() {
       // assert takeLock.isHeldByCurrentThread();
       // assert head.item == null;
       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;
   }

在这里插入图片描述

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

同样的线程

对比 ArrayBlockingQueue

ArrayBlockingQueue:

  • 数组Object[] items 存储
  • 元素的添加和移除通过 两个索引来控制takeIndexputIndex
  • 使用一个普通变量存储容器中元素的数量 int count;
  • 添加和移除元素 使用同一个锁 final ReentrantLock lock;
  • 每次移除元素 调用 notFull.signal()
  • 每次添加元素 调用notEmpty.signal()

LinkedBlockingQueue:

  • Node节点存储元素
  • 元素的添加移除 通过head/last节点的指向控制
  • 通过一个原子操作获取容器元素的数量 final AtomicInteger count = new AtomicInteger();
  • 使用两个锁,移除元素 private final ReentrantLock takeLock 添加元素 private final ReentrantLock putLock
  • 添加元素、移除元素 走双重判断唤醒

LinkedBlockingQueue中的移除和添加元素的方法,都进行了两步操作。

添加元素

 final ReentrantLock putLock = this.putLock;
 putLock.lock();
 try {
     if (count.get() < capacity) {
         enqueue(node);
         c = count.getAndIncrement();
         // 判断队列是否已满
         if (c + 1 < capacity)
         	// putLock 
             notFull.signal();
     }
 } finally {
     putLock.unlock();
 }
 //判断队列是否为空
 if (c == 0)
 	// takeLock
     signalNotEmpty();

移除元素

 final ReentrantLock takeLock = this.takeLock;
 takeLock.lock();
 try {
     if (count.get() > 0) {
         x = dequeue();
         c = count.getAndDecrement();
         if (c > 1)	
         	//takeLock 
             notEmpty.signal();
     }
 } finally {
     takeLock.unlock();
 }
 if (c == capacity)
	 //putLock
     signalNotFull();

LinkedBlockingQueue的双重判断是什么原因呢:

  1. 由于链表的特性所以 使用了两个锁,来优化队列操作的效率
  2. 使用了两个锁也带来了新的问题,队列的操作也需要获取两个锁。
  • 元素移除首先获取锁 takeLock,先移除元素,然后再获取锁putLock,唤醒因队列已满而被阻塞的线程(put/offer)。
  • 这样会导致明明队列未满,却依旧需要不断的试图唤醒被阻塞线程。所以执行了一个判断 if (c == capacity) 当满足指定条件时需要唤醒线程(队列未满)。
  • 这样又存在了另一个问题。假设现在队列已满 同时存在两个因put操作阻塞的线程,那么执行take操作之后 signalNotFull 只会唤醒一个线程。所以需要另一个操作来唤醒其他被阻塞线程
     if (c + 1 < capacity)
    	// putLock 
        notFull.signal();
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值