JDK源码系列 ArrayBlockingQueue源码浅析

前言

在之前分析线程池ThreadExecutorPool的时候,我们就有接触到阻塞队列这一个概念。所谓阻塞队列,就是一个在传统队列基础上,支持两个附加操作的队列。而这两个附加的操作支持阻塞的插入和移除方法。

  1. 支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满为止。
  2. 支持阻塞的移除方法:当队列为空时,获取元素的线程会被阻塞,直至队列非空为止。

说完关于阻塞队列的一些概念,我们就直接切入正题,JUC包提供了多种阻塞队列的具体实现类,而我们就以其中的ArrayBlockingQueue(一个由数组结构组成的有界阻塞队列)类进行分析,来了解阻塞队列的实现机制。


ArrayBlockingQueue源码分析

一、继承结构图

在这里插入图片描述
我们可以看到,ArrayBlockingQueue继承了AbstractQueue,说明ArrayBlockingQueue具备普通队列的基本操作,在基本队列的基础上,ArrayBlockingQueue还实现了BlockingQueue接口。

我们来看一下BlockingQueue接口的实现。

public interface BlockingQueue<E> extends Queue<E> {
	/*将元素e插入阻塞队列中,若超过队列容量限制,那么会抛出异常*/
    boolean add(E e);
    /**
     *  将元素e插入阻塞队列中,若超过队列容量限制,那么会返回fasle
	 *	当使用有界阻塞队列时,offer()方法优于add()
	 */
    boolean offer(E e);
    /*将元素e插入阻塞队列中,若超过队列容量限制,那么当前线程会被阻塞,直至队列不满/被中断*/
    void put(E e) throws InterruptedException;
    /*将元素e插入阻塞队列中,若超过队列容量限制,那么当前线程会被阻塞,直至队列不满/被中断/超时退出*/
    boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException;
    /*移除队列的首部,若队列为空,那么当前线程会被阻塞,直至队列不为空/被中断*/
    E take() throws InterruptedException;
    /*移除队列的首部,若队列为空,那么当前线程会被阻塞,直至队列不为空/被中断/超时退出*/
    E poll(long timeout, TimeUnit unit)
        throws InterruptedException;
    /*队列的剩余容量*/
    int remainingCapacity();
    /*从队列中移除某个指定元素*/
    boolean remove(Object o);
    /*判断队列中是否包含某个执行元素*/
    public boolean contains(Object o);
    /*移除此队列中所有可用的元素,并将它们添加到给定collection 中。*/
    int drainTo(Collection<? super E> c);
    /*最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定collection中。*/
    int drainTo(Collection<? super E> c, int maxElements);
}

以上便是BlockingQueue的接口实现,接下来我们来正式分析ArrayBlockingQueue。


二、ArrayBlockingQueue
2.1. 参数介绍
	/*队列的底层存储元素的结构,一个Object数组*/
    final Object[] items;
    /*下一次执行take,poll,peek或者remove操作的元素的位置*/
    int takeIndex;
    /*下一次执行put、offer或者add操作的元素的位置*/
    int putIndex;
	/*队列中的元素数量*/
    int count;
   	/*队列的主锁*/
    final ReentrantLock lock;
    /*若队列为空,那么在执行获取元素的线程将在该Condition队列上等待*/
    private final Condition notEmpty;
    /*若队列满了,那么执行插入操作的线程将在该Condition队列上等待*/
    private final Condition notFull;

2.2. 构造函数
public ArrayBlockingQueue(int capacity) {
    this(capacity, false);
}

该构造函数指定了队列的大小,并指定队列使用的锁是非公平锁。

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

该构造函数可以指定队列的大小,并且指定队列使用的锁的类型。

public ArrayBlockingQueue(int capacity, boolean fair,
                          Collection<? extends E> c) {
    this(capacity, fair);
    final ReentrantLock lock = this.lock;
    lock.lock(); 
    try {
        int i = 0;
        try {
            for (E e : c) {
                checkNotNull(e);
                items[i++] = e;
            }
        } catch (ArrayIndexOutOfBoundsException ex) {
            throw new IllegalArgumentException();
        }
        count = i;
        putIndex = (i == capacity) ? 0 : i;
    } finally {
        lock.unlock();
    }
}

该构造函数可以指定队列的大小,并且指定队列使用的锁的类型。还可以将指定集合的元素全部插入队列中。

2.3. 核心API实现

首先来看一下阻塞队列的插入函数。

boolean offer(E e)

public boolean offer(E e) {
	/*检查待插入元素是否为null*/
    checkNotNull(e);
    /*加锁*/
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    	/*如果队列中元素的数量超过容量限制*/
        if (count == items.length)
            return false;
        else {
        	/*插入元素*/
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}

offer的执行流程:

  1. 检查插入元素是否为null,若是,则抛出空指针异常。
  2. 加锁
  3. 如果队列满了,那么直接返回fasle,否则,调用enqueue(e)方法插入元素并返回true。
  4. 解锁

我们来看一下具体的插入过程:enqueue(e)

private void enqueue(E x) {
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    /*获取底层数组*/
    final Object[] items = this.items;
    /*将元素x插入数组*/
    items[putIndex] = x;
    /*调整putIndex,记住,该队列是一个环形队列*/
    if (++putIndex == items.length)
        putIndex = 0;
    /*队列元素数量+1*/
    count++;
    /*唤醒等待获取元素的线程,只唤醒一个线程*/
    notEmpty.signal();
}

将有元素插入队列中,执行完插入操作之后,唤醒等待获取元素的线程(Condition队列[notEmpty]中的第一个结点)去尝试获取元素。


add(E e):抛出异常的插入操作

public boolean add(E e) {
	return super.add(e);
}

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

可以看到,add其实是调用offer函数,只是add操作会根据offer操作的返回值来确定是否抛出异常。


put(E e): 阻塞的插入操作

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    /*加锁,对中断敏感,会响应中断*/
    lock.lockInterruptibly();
    try {
    	/*如果队列的元素数量已经超过容量限制,将调用该方法的线程进行阻塞*/
        while (count == items.length)
            notFull.await();
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

put操作其实就是在offer操作的基础上,多了阻塞当前线程的插入操作的步骤,当队列满了,会将当前线程加入到notFull这个Condition队列中。

offer(E e, long timeout, TimeUnit unit)在这里不再赘述,只是多了个超时退出的环节。

讲完队列的插入操作,接着我们来了解一下阻塞队列获取元素的操作。

E take()

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
    	/*若当前队列为空,将当前线程阻塞*/
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}

了解一下具体的获取过程:

private E dequeue() {
    // assert lock.getHoldCount() == 1;
    // assert items[takeIndex] != null;
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    E x = (E) items[takeIndex];
    /*在更新takeIndex之前把原先的位置设置为null,避免无意识的对象持有导致内存泄露*/
    items[takeIndex] = null; //help gc
    /*更新takeIndex*/
    if (++takeIndex == items.length)
        takeIndex = 0;
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    /*唤醒等待插入元素的线程*/
    notFull.signal();
    return x;
}

dequeue()的流程也很简单,不再赘述。我们注意一下items[takeIndex] = null操作,当我们将元素从队列中弹出时需要将原先的位置设置为null,避免无意识的对象持有导致内存泄露。

获取元素操作的阻塞版本和定时版本和插入元素的相似。

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}

/*直接返回,不会被阻塞*/
public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();
    }
}

/*定时等待版本*/
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) {
        	/*若超时,则直接返回null*/
            if (nanos <= 0)
                return null;
            /*当前线程在Condition队列上超时等待*/
            nanos = notEmpty.awaitNanos(nanos);
        }
        return dequeue();
    } finally {
        lock.unlock();
    }
}

执行take操作的线程会一直阻塞在notEmpty这个Condition队列上,等待其他线程将它唤醒。
而执行poll(long timeout, TimeUnit unit)操作的线程被阻塞一段时间,如果这段时间内未获取元素,那么会直接返回null。

接下来我们看一下remove函数的实现:

public boolean remove(Object o) {
    if (o == null) return false;
    final Object[] items = this.items;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    	/*遍历队列已插入的元素*/
        if (count > 0) {
            final int putIndex = this.putIndex;
            int i = takeIndex;
            do {
            	/*若匹配,执行移除操作*/
                if (o.equals(items[i])) {
                    removeAt(i);
                    return true;
                }
                if (++i == items.length)
                    i = 0;
            } while (i != putIndex);
        }
        return false;
    } finally {
        lock.unlock();
    }
}

remove操作会遍历takeIndex到putIndex之间(环形结构)的元素并一一进行比较,若匹配,会调用removeAt(i)执行真正的移除操作。

void removeAt(final int removeIndex) {
    final Object[] items = this.items;
    /*如果移除的元素目标刚好为takeIndex,那么就不需要进行队列的调整*/
    if (removeIndex == takeIndex) {
       	/*直接将takeIndex位置上的对象置为null*/
        items[takeIndex] = null;
        /*更新takeIndex*/
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
    } else {
        /*将removeIndex和putIndex的元素(环形)进行调整,即前移一个位置*/
        for (int i = removeIndex;;) {
            int next = i + 1;
            if (next == items.length)
                next = 0;
            if (next != putIndex) {
                items[i] = items[next];
                i = next;
            } else {
                items[i] = null;
                this.putIndex = i;
                break;
            }
        }
        count--;
        if (itrs != null)
            itrs.removedAt(removeIndex);
    }
    /*唤醒等待插入元素的线程*/
    notFull.signal();
}

阻塞队列的其他API在这里就不再分析了,我们已经大概了解阻塞队列是如何实现支持阻塞的插入和获取操作。
他通过了ReentrantLock和Condition,使用了 “生产者-消费者” 的多线程设计模式来实现了这两种操作。

至此,阻塞队列ArrayBlockingQueue的分析已经完毕。

我们利用阻塞队列来实现一个生产者-消费者的例子:

产品:

@ToString
@Data
@AllArgsConstructor
public class Product {
    private String name;
}

生产者:

public class Producer implements Runnable{
    private static AtomicInteger count = new AtomicInteger(0);
    private static volatile boolean isRunning = true;
    private BlockingQueue<Product> queue;
    private static final long EXECUTION_TIME = 1000;
    private static Random random = new Random();
    public Producer(BlockingQueue<Product> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程准备开始生产产品");
        while (isRunning){
            try {
                TimeUnit.MILLISECONDS.sleep(random.nextInt((int)EXECUTION_TIME) + EXECUTION_TIME);
                String productName = "产品" + count.getAndIncrement();
                Product product = new Product(productName);
                if(!queue.offer(product)){
                    System.out.println("产品已经满了!" + Thread.currentThread().getName() + "准备阻塞");
                    queue.put(product);
                }
                System.out.println(product + "生产完毕");
            } catch (InterruptedException e) {
                System.out.println("当前线程被中断");
                Thread.currentThread().interrupt();
            }
        }
    }
    public void stop(){
        isRunning = false;
    }
}

消费者:

public class Consumer implements Runnable{
    private static volatile boolean isRunning = true;
    private final BlockingQueue<Product> queue;
    private static final long EXECUTION_TIME = 1000;
    private static Random random = new Random();
    public Consumer(BlockingQueue<Product> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "准备开始消费产品");
        while (isRunning){
            try {
                TimeUnit.MILLISECONDS.sleep(EXECUTION_TIME);
                Product product = null;
                if((product = queue.poll()) == null){
                    System.out.println("产品为空," + Thread.currentThread().getName() + "准备阻塞");
                    product = queue.take();
                }
                System.out.println(product + "被消费");
            } catch (InterruptedException e) {
                System.out.println("线程被中断");
                Thread.currentThread().interrupt();
            }
        }
    }
    public void stop(){
        isRunning = false;
    }
}

主函数:

public class Main {
    static class CThreadFactory implements ThreadFactory{
        private AtomicInteger count = new AtomicInteger(1);
        @Override
        public Thread newThread(Runnable runnable) {
            return new Thread(runnable, "消费者线程" + count.getAndIncrement());
        }
    }
    static class PThreadFactory implements ThreadFactory{
        private AtomicInteger count = new AtomicInteger(1);
        @Override
        public Thread newThread(Runnable runnable) {
            return new Thread(runnable, "生产者线程" + count.getAndIncrement());
        }
    }
    private static final int CONSUMER_NUM = 5;
    private static final int PRODUCER_NUM = 10;
    private static ExecutorService consumerService = Executors.newFixedThreadPool(CONSUMER_NUM, new CThreadFactory());
    private static ExecutorService producerService = Executors.newFixedThreadPool(PRODUCER_NUM, new PThreadFactory());
    public static void main(String[] args) {
        try {
            ArrayBlockingQueue<Product> queue = new ArrayBlockingQueue<>(10);
            for (int i = 0; i < PRODUCER_NUM; i++) {
                producerService.execute(new Producer(queue));
            }
            for (int i = 0; i < CONSUMER_NUM; i++) {
                consumerService.execute(new Consumer(queue));
            }
        }finally {
            consumerService.shutdown();
            producerService.shutdown();
        }
    }
}

以上便是我们利用阻塞队列来实现的消费者-生产者。我们还可以使用java内部机制notify/wait或者JUC包的await/signal。这里便不再多说。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值