浅析BlockingQueue实现

BlockingQueue

BlockingQueue位于juc包,常用语并发的生产者、消费者场景,与普通queue相比,增加两个put,take操作:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。

核心方法

add(E e) 添加元素有空间添加成功返回true,没空间抛IllegalStateException
put(E e) 将元素插入队列,如没空间将阻塞等待可用空间。
take() 获取并移除队列头元素,如果不可用则阻塞等待
poll(long timeOut,TimeUnit unit) 获取移除头元素,指定时间内等待,到期超时返回null。

 

具体实现类

    BlockingQueue的具体实现类可参考API,这里重点从源码说明ArrayBlockingQueue、LinkedBlockingQueue。从名字可知类内部前后分别通过数组、链表方式实现。


浅析BlockingQueue实现
 

LinkedBlockQueue源码分析

    队列Node节点

 

 /**
         * 
         * 代表队列元素的内部类
         */
static class Node<E> {
         //泛型指定一个节点的值
        E item;
        //表示指针的引用,指向下一个队列元素。
        Node<E> next;
        //构造函数
        Node(E x) { item = x; }
    }

 

 

    Node是Queue的一个内部类,代表队列中一个节点,此节点补单包含存储的对象值,还包含一个指向下一个节点的引用,这样第一个节点,指向第二个,一次类推,形成一个似链条一样的链。

    第一个元素称作:头,最后一个称作:尾,如果只有一个元素即是头又是尾。

浅析BlockingQueue实现

   构造函数

      默认创建Integer.MAX_VALUE容量的队列,头元素和尾元素指向同一个没有值的Node。

 

/**
     * 创建一个LinkedBlockingQueue,默认容量整数最大值
     */
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

   /**
     * 创建指定容量的LinkedBlockingQueue
     */
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

 Put方法

    

//成员变量锁、容量计数器
private final ReentrantLock putLock = new ReentrantLock();
private final AtomicInteger count = new AtomicInteger(0);

public void put(E e) throws InterruptedException {
        //队列不可以插入null
        if (e == null) throw new NullPointerException();
      
        int c = -1;
        //获取重入锁
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            //如果元素数量等于容量,即队列已满无空间,则阻塞。
            while (count.get() == capacity) { 
                    notFull.await();
            }
            //添加元素
            enqueue(e);
            //元素个数+1
            c = count.getAndIncrement();
            //添加成功,判断是达到最大容量
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
    }
//队尾指针指向新添加元素
 private void enqueue(E x) {
        // assert putLock.isHeldByCurrentThread();
        last = last.next = new Node<E>(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();
//元素个数减1
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

 

/**移除头部元素*/
private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        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;
    }

 

ArrayBlocklingQueue源码分析

    与普通队列相比,其put、take方法法支持阻塞,下面看下这两个方法源码如下:

 

public void put(E e) throws InterruptedException {
       //null元素不能插入
        if (e == null) throw new NullPointerException();
        final E[] items = this.items;
        //获取锁       
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            try {
                //元素个数到达数组容量阻塞
                while (count == items.length)
                    notFull.await();
            } catch (InterruptedException ie) {
                notFull.signal(); // propagate to non-interrupted thread
                throw ie;
            }
            //有空间,添加元素
            insert(e);
        } finally {
            lock.unlock();
        }
    }

    

private void insert(E x) {
        items[putIndex] = x;
        putIndex = inc(putIndex);
        ++count;
        notEmpty.signal();
    }

 

/**
*首先获取锁,然后判断空间是否有元素,有移除并返回,释放锁
*/
public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            try {
                //队列空,内部数组不含任何元素时阻塞
                while (count == 0)
                    notEmpty.await();
            } catch (InterruptedException ie) {
                notEmpty.signal(); // propagate to non-interrupted thread
                throw ie;
            }
            E x = extract();
            return x;
        } finally {
            lock.unlock();
        }
    }

 

小结:

      从上面的简单源码分析看,链表、数组实现程序的空值流程基本一致,获取锁,添加,移除元素,释放锁,唯一不同点在于链表内部维护指针,数组内部维护索引,具体采用哪一个较好,还是根据业务场景,看二者的put、take方法的时间复杂度。

 

你可能感兴趣的:(BlockingQueue)