阻塞队列源码阅读记录

本文结合生产者消费者模式对阻塞队列的主要方法实现进行说明。

一、LinkedBlockingQueue

该队列维护的临界区由链表实现,主要成员变量如下:

capacity:临界区的上限

count:原子整型,临界区的实际大小

head:指向队首节点,控制出队过程

tail:指向队尾节点,控制入队过程

notEmpty:非空等待条件,从临界区中取资源时,若无资源则在该条件上等待

takeLock:控制每次只有一个线程从临界区中取资源

notFull:非满等待条件,往临界区中存资源时,若无空位(资源数量已经达到上限)则在该条件上等待

putLock:控制每次只有一个线程往临界区中存资源

take方法,从临界区中取资源(消费者消费资源)

主要步骤为:

1.判断临界区中是否有资源,若无则在notEmpty条件上等待,否则进入2

 2.通过临界区链表的头指针head将队首资源出队,资源计数count减一

3.若take操作之前资源数等于资源上限,则说明此次操作后临界区中将有空位存在,此时唤醒等待在notFull条件上的线程

    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上阻塞时,若某个生产者线程调用put方法往临界区中存资源,该线程将调用notEmpty.signal唤醒阻塞的消费者线程,但只会唤醒一个线程(不会调用signalAll,因为只增加了一个资源,若将阻塞消费者线程全部唤醒,大部分线程还是会因抢不到资源而继续阻塞,这样产生了无意义的上下文切换)
            //消费者取资源后,若临界区中还有资源,则链式唤醒其他的消费者
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        //若取资源之前,资源数达到上限,说明此次取出后临界区中存在空位,此时唤醒一个阻塞的生产者
        //为什么c < capacity时不唤醒生产者,因为c == capacity时只要唤醒了某个生产者,该生产者每次生产资源后会判断临界区中的资源数,若未达到上限,则链式唤醒其他生产者
        //这样同样保证了阻塞的线程必定有机会被唤醒,另外消费者调用signalNotFull方法去唤醒生产者,需要加锁和解锁,而生产者调用notFull.signal唤醒生产者时,已经获取了锁,不必进行额外的加解锁操作
        if (c == capacity)
            signalNotFull();
        return x;
    }

put方法,往临界区中存资源(生产者生产资源),与take方法基本对称

二、ArrayBlockingQueue

该队列维护的临界区由数组实现,与LinkedBlockingQueue的主要区别为该队列只有一把锁,直接锁住整个数组,因此所有操作均为串行,首尾指针由数组和首尾索引代替,数组为“循环数组”

take方法和put方法

很简单的判断临界区资源数量,决定是否阻塞,count保证了队尾索引无论如何“前移”,均不会超越队尾指针

入队时插入资源,队尾索引”前移“

出队时置当前队首索引处资源为空,队首索引“前移”

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

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

猜你喜欢

转载自blog.csdn.net/qq_37720278/article/details/82963883