深入探究LinkedBlockingQueue、ArrayBlockingQueue源码

目录

一、LinkedBlockingQueue

1、类图 

2、有界队列

3、单向队列

4、非阻塞方法

signalNotEmpty()

signalNotFull()

enqueue()

dequeue()

fullyLock()

fullyUnlock()

size()

remainingCapacity()

offer()

poll()

peek()

5、阻塞方法

offer(E, long, TimeUnit)

poll(long, TimeUnit)

take()

put()

二、 ArrayBlockingQueue

1、类图

2、非阻塞方法

dec()

itemAt()

enqueue()

dequeue

add()

offer()

poll()

peek()

3、阻塞方法

put()

take()

poll(long, TimeUnit)


一、LinkedBlockingQueue

LinkedBlockingQueue是基于单向链表实现的(有界/无界)阻塞队列,通过ReentrantLock保证存/取元素的并发安全,在线程池TheadPoolExecutor中的workQueue就是一个LinkedBlockingQueue的实例。

思考:为什么说LinkedBlockingQueue是一个队列?

1、LinkedBlockingQueue实现了Queue接口

2、根据数据结构中队列的特点判断:先进先出(FIFO),队尾进,队头出。

- LinkedBlockingQueue中的插入方法offer()、put()都是在队尾添加元素。

- LinkedBlockingQueue中的获取/删除方法peek()、poll()、take()都是在队头获取/删除元素。

与普通队列相比,线程池使用LinkedBlockingQueue作为缓存队列的好处是:

  • 当队列已满时,会阻塞添加任务的线程(放到条件变量ConditionObject的条件队列里),而不用丢弃当前线程;
  • 当队列为空时,会阻塞获取任务的线程(放到条件变量ConditionObject的条件队列里),而不用丢弃当前线程;

在这篇文章中,会详细介绍LinkedBlockingQueue的底层实现原理。

在此之前,你需要了解ReentrantLock、ConditionObject以及LockSupport几个并发相关的API

1、类图 

为了方便快速了解其结构,简单画了一下的LinkedBlockingQueue类图

深入探究LinkedBlockingQueue、ArrayBlockingQueue源码_第1张图片

通过上面类图可以了解到,LinkedBlokingQueue中依赖了ReentrantLock来保证入队(putLock)和出队(takeLock)的线程安全,同时通过Condition(条件变量)来保存被阻塞的线程:

  • 调用take()方法时因队列为空而阻塞的线程(对应条件变量为notEmpty)
  • 调用put()方法时因队列已满而阻塞的线程(对应条件变量为notFull)。

LinkedBlokingQueue源码中的成员变量声明:

    /** 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();

2、有界队列

LinkedBlokingQueue是一个有界队列,因为它内部通过int类型的capacity属性来保存当前队列的容量,当队列中元素的个数等于capacity时,无法再往队列里添加元素。

可以通过实例化时传入int类型参数指定capacity的值,当通过无参构造方法实例化时,队列的容量为Integer.MAX_VALUE,而此时LinkedBlokingQueue是一个无界队列。

    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) {
            throw new IllegalArgumentException();
        }

        this.capacity = capacity;
        last = head = new Node(null);
    }

3、单向队列

LinkedBlokingQueue是一个单向队列,因为其内部定义的Node是一个单向的链表,并且LinkedBlokingQueue只通过head和last保存了队头和队尾节点。

    /**
     * Linked list node class
     */
    static class Node {
        E item;

        Node next;

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

4、非阻塞方法

这个章节介绍LinkedBlokingQueue中的非阻塞的方法,调用非阻塞方法时,添加/获取元素失败时会直接返回,而不会阻塞。

signalNotEmpty()

加锁唤醒一个(队列满了的时候)调用put()方法被阻塞的线程

    private void signalNotEmpty() {
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();

        try {
            notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
    }

signalNotFull()

加锁唤醒一个(队列为空时)调用take()方法被阻塞的线程

    private void signalNotFull() {
        final ReentrantLock putLock = this.putLock;
        putLock.lock();

        try {
            notFull.signal();
        } finally {
            putLock.unlock();
        }
    }

enqueue()

简单的入队操作

    private void enqueue(Node node) {
        last = last.next = node;
    }

dequeue()

出队操作,这个方法的代码涉及到GC,有点复杂,看不懂的直接跳过,不是一个初中级程序员能够理解的。

    private E dequeue() {
        Node h = head;
        Node first = h.next;
        h.next = h; // help GC
        head = first;
        E x = first.item;
        first.item = null;

        return x;
    }

fullyLock()

加上所有锁,执行该方法后,其他线程的入队出队操作获取锁都会失败。

    void fullyLock() {
        putLock.lock();
        takeLock.lock();
    }

fullyUnlock()

释放所有锁,配合fullyLock()使用

    void fullyUnlock() {
        takeLock.unlock();
        putLock.unlock();
    }

size()

获取队列当前长度

    public int size() {
        return count.get();
    }

remainingCapacity()

获取队列当前可用容量,即队列的容量减去当前队列中元素的个数。

    public int remainingCapacity() {
        return capacity - count.get();
    }

offer()

往队尾添加元素,如果队列已满,则直接返回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节点
        Node node = new Node(e);

        // 加锁
        final ReentrantLock putLock = this.putLock;
        putLock.lock();

        try {
            if (count.get() < capacity) {
                // 入队
                enqueue(node);

                // 队列长度自增1
                c = count.getAndIncrement();

                // 如果队列还没有满,唤醒notFull中因为添加失败被阻塞的一个线程
                if (c + 1 < capacity) {
                    notFull.signal();
                }
            }
        } finally {
            // 释放锁
            putLock.unlock();
        }

        // 如果入队之前队列为空,则入队之后队列中有一个元素
        // 唤醒一个因为调用take()方法被阻塞的线程
        if (c == 0) {
            signalNotEmpty();
        }

        return c >= 0;
    }

poll()

在队头获取并删除一个元素,如果队列为空,直接返回null。

    public E poll() {
        final AtomicInteger count = this.count;
        
        // 队列没有元素,返回null
        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();
                // 队列长度自减1
                c = count.getAndDecrement();
                
                // 队列不为空
                // 唤醒notEmpty中因为队列为空,即通过take()获取元素失败而被阻塞的一个线程
                if (c > 1) {
                    notEmpty.signal();
                }
            }
        } finally {
            takeLock.unlock();
        }

        // 如果出队之前队列是满的,则出队之后队列中还有一个可用的位置
        // 唤醒一个因为调用put()方法被阻塞的线程
        if (c == capacity) {
            signalNotFull();
        }

        return x;
    }

peek()

从队头获取一个元素,不删除元素。

这个方法非常简单,加锁获取队列的头结点,如果队列为空返回null。

    public E peek() {
        if (count.get() == 0)
            return null;

        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();

        try {
            Node first = head.next;

            if (first == null)
                return null;
            else
                return first.item;
        } finally {
            takeLock.unlock();
        }
    }

5、阻塞方法

offer(E, long, TimeUnit)

    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循环后面的代码会再次尝试入队
            while (count.get() == capacity) {
                if (nanos <= 0) {
                    return false;
                }

                nanos = notFull.awaitNanos(nanos);
            }

            // 阻塞完指定时间之后,再次尝试入队
            enqueue(new Node(e));

            // 获取并自增队列长度相当于c=c++
            // AtomicInteger里的getAndXxx()方法是先获取后操作
            c = count.getAndIncrement();

            // 如果这时候队列有空闲了,唤醒一个入队时被阻塞的线程
            // 在入队阻塞的这段时间,其他线程执行出队操作,因为只加了putLock,并不影响其他线程的出队操作
            if (c + 1 < capacity) {
                notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
        
        // 入队成功之后,当前队列中最少有一个元素,唤醒一个出队的线程
        // 注意:这里的c是自增之前的队列元素个数,自增之后,队列中元素个数是1
        if (c == 0) {
            signalNotEmpty();
        }

        return true;
    }

poll(long, TimeUnit)

    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) {
                    return null;
                }

                nanos = notEmpty.awaitNanos(nanos);
            }

            // 阻塞完之后重新尝试出队
            x = dequeue();

            // 获取并自减队列的元素个数
            c = count.getAndDecrement();

            // 如果原来的队列元素个数大于1,则递减之后元素大于0,唤醒一个获取元素被阻塞的线程
            if (c > 1) {
                notEmpty.signal();
            }
        } finally {
            takeLock.unlock();
        }

        // 如果原来的队列是满的,出队之后队列有一个空位,唤醒一个入队被阻塞的线程
        if (c == capacity) {
            signalNotFull();
        }

        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();

            // 队列不为空,唤醒notEmpty中的一个线程
            if (c > 1) {
                notEmpty.signal();
            }
        } finally {
            takeLock.unlock();
        }

        if (c == capacity) {
            signalNotFull();
        }

        return x;
    }

notEmpty.await();这行代码完成了阻塞当前线程,我们看一下他的实现

因为notEmpty是调用ReentrantLock的newCondition()方法得到的,所以用的是AQS的内部Condition实现类ConditionObject。

        public final void await() throws InterruptedException {
            // 如果当前线程被中断了,清除中断状态,抛出中断异常返回
            if (Thread.interrupted()) {
                throw new InterruptedException();
            }

            // 把当前线程放到条件队列中
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;

            // node节点已经在条件队列中
            while (!isOnSyncQueue(node)) {
                // 中断线程
                LockSupport.park(this);

                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

所以,最终是通过LockSupport.part()方法来中断线程的,对应的signal()和signalAll()方法也是通过LockSupport.unpark()方法来唤醒线程。

        public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            
            if (first != null)
                doSignal(first);
        }

        private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                
                first.nextWaiter = null;
            } while (!transferForSignal(first) && (first = firstWaiter) != null);
        }

        final boolean transferForSignal(Node node) {
            if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
                return false;

            Node p = enq(node);
            int ws = p.waitStatus;

            if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
                LockSupport.unpark(node.thread); // 唤醒线程
            
            return true;
        }

put()

往队尾添加元素,如果队列已满,则阻塞当前线程。

    public void put(E e) throws InterruptedException {
        // 添加的元素不能为空
        if (e == null) 
            throw new NullPointerException();

        int c = -1;
        Node node = new Node(e);

        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();

        try {
            // 队列已满,阻塞线程
            while (count.get() == capacity) {
                notFull.await();
            }

            // 入队
            enqueue(node);
            // 队列长度自增
            c = count.getAndIncrement();

            // 如果队列还没有满,唤醒notFull中因为添加失败被阻塞的一个线程
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }

        if (c == 0)
            signalNotEmpty();
    }

二、 ArrayBlockingQueue

学习完LinkedBlockingQueue之后,会发现ArrayBlockingQueue非常简单。

ArrayBlockingQueue和LinkedBlockingQueue的不同点:

1、ArrayBlockingQueue底层基于数组实现;LinkedBlockingQueue底层是单向链表;

2、ArrayBlockingQueue只通过一个ReentrantLock对象保证线程安全;LinkedBlockingQueue通过两个ReentrantLock对象takeLock和putLock分别保证出队/入队操作的线程安全;

3、ArrayBlockingQueue可以指定ReentrantLock是公平锁FairSync还是非公平锁NonFairSync;LinkedBlockingQueue中的ReentrantLock是默认的非公平锁NonFairSync;

4、由于数组结构比较简单,ArrayBlockingQueue的出/入队操作也更简单;

好了,接下来开始学习ArrayBlockingQueue

1、类图

深入探究LinkedBlockingQueue、ArrayBlockingQueue源码_第2张图片

2、非阻塞方法

dec()

递减i,如果到了数组末尾,则重新从数组下标0开始

    final int dec(int i) {
        return ((i == 0) ? items.length : i) - 1;
    }

itemAt()

获取队列指定位置的元素

    final E itemAt(int i) {
        return (E) items[i];
    }

enqueue()

入队操作。

    private void enqueue(E x) {
        // 得到队列中保存元素的数组
        final Object[] items = this.items;
        // 把元素放到数组下一个存放元素的位置
        items[putIndex] = x;

        // putIndex自增1,如果自增1之后达到了数组的长度,则重新设置为0
        if (++putIndex == items.length) {
            putIndex = 0;
        }

        // 数组长度自增1
        count++;
        // 唤醒一个获取元素被阻塞的线程
        notEmpty.signal();
    }

dequeue

出队操作。

    private E dequeue() {
        // 获取队列内部的数组
        final Object[] items = this.items;

        // 得到并删除数组中下标为takeIndex的值
        E x = (E) items[takeIndex];
        items[takeIndex] = null;

        // 循环数组
        if (++takeIndex == items.length) {
            takeIndex = 0;
        }

        // 数组长度减一
        count--;

        if (itrs != null) {
            itrs.elementDequeued();
        }

        // 唤醒一个添加元素被阻塞的线程
        notFull.signal();

        return x;
    }

add()

往队列添加元素,如果队列已满,抛出异常

    public boolean add(E e) {
        if (offer(e)) {
            return true;
        } else {
            throw new IllegalStateException("Queue full");
        }
    }

offer()

往队列添加元素,如果队列已满,返回false

    public boolean offer(E e) {
        if (e == null) {
            throw new NullPointerException();
        }

        final ReentrantLock lock = this.lock;
        lock.lock();

        try {
            if (count == items.length)
                return false;
            else {
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }

poll()

获取元素,如果队列为空,直接返回null

    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();

        try {
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }   

peek()

获取元素,如果队列为空,直接返回null

    public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();

        try {
            return itemAt(takeIndex);
        } finally {
            lock.unlock();
        }
    }

3、阻塞方法

put()

添加元素,如果队列已满则阻塞当前添加元素的线程。

    public void put(E e) throws InterruptedException {
        if (e == null) {
            throw new NullPointerException();
        }

        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();

        try {
            // 队列已满,阻塞
            while (count == items.length) {
                notFull.await();
            }
            
            // 入队
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

take()

获取元素,如果队列为空则阻塞当前获取元素的线程。

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();

        try {
            while (count == 0) {
                notEmpty.await();
            }

            return dequeue();
        } finally {
            lock.unlock();
        }
    }

poll(long, TimeUnit)

获取元素,如果队列为空,会自旋获取指定的时间单位,如5秒。

    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();

        try {
            while (count == 0) {
                if (nanos <= 0) {
                    return null;
                }

                nanos = notEmpty.awaitNanos(nanos);
            }

            return dequeue();
        } finally {
            lock.unlock();
        }
    }

你可能感兴趣的:(java,开发语言)