BlockingQueue之ArrayBlockingQueue实现原理

BlockingQueue之ArrayBlockingQueue

ArrayBlockingQueue是一个基于数组实现的有界限的阻塞队列,队列中的元素也是先进先出。

在说ArrayBlockingQueue之前,先来看看其接口BlockingQueue,接口中提供了一些比较有意思的方法,当我们调用其中的方法时,会表现出一下四种行为之一:

  • 直接抛出一个异常(Throws Exception)
  • 返回一个特殊的值(Special value)
  • 一直处于阻塞状态(Blocks)
  • 超时等待(Times out)

而对于插入(Insert)和移除操作(Remove)分别用如下几个方法体现上述的行为。

Insert:

  • add(e) : 当队列空间满时调用这个方法会直接抛出一个异常
  • offer(e):当队列满时调用这个方法会直接返回一个false
  • put(e):当队列满时调用这个方法会一直处于阻塞状态,直到有空的空间可以插入数据
  • offer(Object, long, TimeUnit):当队列满时,会等待一段时间,如果这段时间队列还是满的,会返回一个false。

Remove:

  • remove():如果没有元素,调用这个方法会抛出异常
  • poll():如果没有元素,调用这个方法会返回null
  • take():如果没有元素,调用这个方法会一直阻塞,知道有元素为止
  • poll(long, TimeUnit):如果没有元素,调用这个方法会阻塞一段时间,如果超时还没有元素,会返回null。

ArrayBlockingQueue的成员变量

final Object[] items

前面说过,元素是放到一个数组中的,items数组就是用来存放数据的

int takeIndex

items index for next take, poll, peek or remove

takeIndex用来取数据的索引,如果调用take、poll等方法就是通过这个索引来定位数据的。

int putIndex

items index for next put, offer, or add

putIndex是定位下一个用来存放数据的位置,通过上面可以看出,插入数据和写入数据是通过两个不同的索引来定位的。

final ReentrantLock lock

Main lock guarding all access

使用Java中的重入锁ReentrantLock来进行操作过程中的同步机制的,翻看源码的话,大部分操作都会使用这个锁来进行加锁和解锁操作。

private final Condition notEmpty

Condition for waiting takes

当调用take方法时,如果队列为null,会调动notEmpty.await()方法让当前线程处于等待状态(让出CPU资源),当有新的数据入队时,会再次调用notEmpty.singal()方法让阻塞的线程继续运行。

private final Condition notFull

Condition for waiting puts

当调用put方法时,如果队列已满,会调用notFull.await()方法,让当前线程处于等待状态(让出CPU资源),当有新的元素出队时,会再次调用notFull.singal()方法让阻塞的线程继续运行。

transient Itrs itrs = null

Shared state for currently active iterators

用于记录集合的迭代器状态,迭代器实现这块代码太多了,我也不想看了,用到时再详细看看吧。

核心方法

插入操作:

// 超时等待方式
public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

    checkNotNull(e);
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    // 如果由于获取锁处于阻塞的话,可以被中断信号打断的
    lock.lockInterruptibly();
    try {
        while (count == items.length) {
            if (nanos <= 0)
                return false;
            // 其实这里内部的核心调用的是LockSupport.parkNanos(this, nanosTimeout);
            //这个方法,而这内部调用的又是UNSAFE.park(false, nanos);这个方法
            nanos = notFull.awaitNanos(nanos);
        }
        // 入队操作,内部会调用notEmpty.signal();这个方法
        enqueue(e);
        return true;
    } finally {
        lock.unlock();
    }
}

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            // 队列满的话调用await方法
            notFull.await();
        enqueue(e);
    } 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) {
            if (nanos <= 0)
                return null;
            // 队列为null,超时等待
            nanos = notEmpty.awaitNanos(nanos);
        }
        return dequeue();
    } finally {
        lock.unlock();
    }
}

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            // 为null,进行等待
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}

案例

实现一个简单的生产者和消费者案例

import java.util.Random;
import java.util.concurrent.*;

/**
 * @Author: jiangcw
 * @Version 1.0
 */
public class Test {

    public static void main(String[] args) throws Exception {
        // 消费者线程池
        ThreadPoolExecutor consumerExecutor = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS,  new ArrayBlockingQueue<Runnable>(1));
        ArrayBlockingQueue<Integer> queue =  new ArrayBlockingQueue<Integer>(1);
        consumerExecutor.execute(()->{
            Consumer consumer = new Consumer(queue);
            while(true) {
                consumer.consume();
            }
        });

        consumerExecutor.execute(()->{
            Consumer consumer = new Consumer(queue);
            while(true) {
                consumer.consume();
            }
        });

        // 生产者线程池
        ThreadPoolExecutor producerExecutor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS,  new ArrayBlockingQueue<Runnable>(1));
        producerExecutor.execute(()-> {
            Producer producer = new Producer(queue);
            Random random = new Random();
            while(true) {
                producer.produce(random.nextInt(100));
                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e) {}
            }
        });
    }

    private static class Consumer {

        private BlockingQueue<Integer> queue;

        Consumer(BlockingQueue<Integer> queue) {
            this.queue = queue;
        }

        // 从队列中取数,像生产环境,很多时候用poll(long, TimeUnit),主要是如果生产者生产慢了,将会导致消费者一直等待,可以选用超时等待模式
        public void consume() {
            try {
                System.out.println("Thread:" + Thread.currentThread().getName() + " 消费了" + queue.take());
            }catch (InterruptedException e) {}
        }
    }

    private static class Producer {
        private BlockingQueue<Integer> queue;

        Producer(BlockingQueue<Integer> queue) {
            this.queue = queue;
        }

        // 往队列中塞数据
        public void produce(Integer i) {
            try {
                queue.put(i);
                System.out.println("Thread:" + Thread.currentThread().getName() + " 生产了 " + i);
            }catch (Exception e) {}
        }
    }
}

总结:

  • 底层是使用数组存储元素的
  • 插入数据和取出数据分别用putIndex和takeIndex来定位位置的
  • 对于方法的访问是通过ReentrantLock来实现同步的
  • 对于取数据和写数据实现阻塞的方式是通过Condition来实现的,而且分别使用不同的Condition
  • 对于实现生产者/消费者模式,ArrayBlockingQueue是一个非常好的数据结构

你可能感兴趣的:(java,计算机)