多线程并发编程13-ArrayBlockingQueue源码剖析

    前面一文介绍了有界链表阻塞队列LinkedBlockingQueue,今天来说一说有界数组阻塞队列ArrayBlockingQueue。

    ArrayBlockingQueue类中的常用方法和前文介绍的LinkedBlockingQueue类常用方法一样,内部源码实现也大同小异。ArrayBlockingQueue类初始化一个固定大小的数组items,使用ReentrantLock保证数据的原子性,相比于LinkedBlockingQueue有两个ReentrantLock对象,ArrayBlockingQueue类中只有一个ReentrantLock对象,读写操作都需要获取同一个ReentrantLock对象。另外,还有两个条件变量,条件变量内部都有一个条件队列用来存放进队和出队阻塞的线程,其实就是生产者-消费者模型。这些主要的成员变量如下:

final Object[]items;

final ReentrantLocklock;

private final ConditionnotEmpty;

private final ConditionnotFull;

offer(E e)

    向队里尾部插入一个元素,如果队列有空闲则插入成功后返回true,如果队列已满则丢弃当前元素并返回false。

public boolean offer(E e) {

//(1)检查e是否为null,是则抛出NullPointerException

    checkNotNull(e);

//(2)尝试获取lock对象。

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

//(3)如果队列已满则丢弃当前元素并返回false

        if (count == items.length)

            return false;

        else {

//(4)队列未满,则在队尾添加元素,并调用notEmpty.signal(),通知阻塞的消费者可以进行消费数据。

            enqueue(e);

            return true;

        }

    } finally {

//(5)释放锁。

        lock.unlock();

    }

}

put(E e)

    向队列队尾插入一个元素,如果队列中有空闲则插入后直接返回,如果队列已满则阻塞当前线程,直到队列中有空闲插入成功后返回。线程阻塞时如果被其他线程设置了中断标志,则该线程会抛出InterruptedException异常。

public void put(E e) throws InterruptedException {

//(1)检查e是否为null,是则抛出NullPointerException

    checkNotNull(e);

//(2)尝试获取锁对象,调用的是lockInterruptibly方法,所以在当其他线程设置了中断标志,该线程会抛出InterruptedException异常。

    final ReentrantLock lock = this.lock;

    lock.lockInterruptibly();

    try {

//(3)队列已满则阻塞当前线程

        while (count == items.length)

            notFull.await();

//(4)队列未满,则在队尾添加元素,并调用notEmpty.signal(),通知阻塞的消费者可以进行消费数据。

        enqueue(e);

    } finally {

//(5)释放锁。

        lock.unlock();

    }

}

 poll() 

    从队列头部获取一个元素,并从队列里移除该元素,如果队列为空则返回null。

public E poll() {

//(1)尝试获取锁。

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

//(2)队列不为空则获取头部元素并移除,调用notFull.signal()方法,通知阻塞的生产者可以生产数据。

        return (count == 0) ? null : dequeue();

    } finally {

//(3)释放锁。

        lock.unlock();

    }

}

peek()

    获取队列头部的元素但不从队列中移除该元素,队列为空的时候返回null。

public E peek() {

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

        return itemAt(takeIndex); // null when queue is empty

    } finally {

        lock.unlock();

    }

}

take()

    获取队列中头节点元素并从队列里移除该节点,如果队列为空则会阻塞当前线程直到队列中有元素。如果当前线程在阻塞时被其他线程设置了中断标志,则会抛出InterruptedException异常。

public E take() throws InterruptedException {

//(1)尝试获取锁对象,调用的是lockInterruptibly方法,所以在当其他线程设置了中断标志,该线程会抛出InterruptedException异常。

    final ReentrantLock lock = this.lock;

    lock.lockInterruptibly();

    try {

//(2)队列为空则进行阻塞等待。

        while (count == 0)

            notEmpty.await();

//(3)队列不为空则获取头部元素并移除,调用notFull.signal()方法,通知阻塞的生产者可以生产数据。

        return dequeue();

    } finally {

//(4)释放锁。

        lock.unlock();

    }

}

size()

    相比于LinkedBlockingQueue类中的size方法,本类中的size需要获取锁,因为本类中的count变量没有volatile变量修饰无法保证内存的可见性。

public int size() {

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

        return count;

    } finally {

        lock.unlock();

    }

}

    今天的分享就到这,有看不明白的地方一定是我写的不够清楚,所有欢迎提任何问题以及改善方法。

发布了16 篇原创文章 · 获赞 19 · 访问量 896

猜你喜欢

转载自blog.csdn.net/zfs_sir/article/details/105058150