阻塞队列ArrayBlockingQueue

Java的阻塞队列,在实现时,使用到了lock和condition,下面是对其主要方法的介绍。

首先看一下,阻塞队列中使用到的锁。

/** Main lock guarding all access */
    final ReentrantLock lock;
​
    /** Condition for waiting takes */
    private final Condition notEmpty;
​
    /** Condition for waiting puts */
    private final Condition notFull;

主要的锁是一个可重入锁,根据注释,它是用来保证所有访问的同步。此外,还有2个condition,notEmpty用于take等待,notFull用于put等待。

两个condition的初始化方法如下:

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

下面介绍一下put方法。代码如下。

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时,首先对待插入的元素进行了非null判断。然后获取锁。之后用一个循环进行判断,如果元素已满,那么,就调用notFull的await方法,进行阻塞。当有别的线程(其实是take元素的线程)调用notFull的siginal方法后,put线程会被唤醒。唤醒后再确认一下count是否小于items.length,如果是,则进行加入队列的操作。

下面介绍一下take方法,代码如下:

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

进行take时,同样先要获取锁,然后判断元素个数是否为0,为0时需要等待在notEmpty条件上,等待被唤醒。唤醒之后,会再进行一次元素个数判断,然后进行出队列操作。

分析代码到这里的时候,我产生了一个疑问,如果当前队列慢了,执行put的线程在获取到锁之后,等待notFull条件上。那么,当执行take操作的线程想获取锁时,阻塞队列的锁已经被前面put的线程获取了,那么take将永远得不到机会执行。怎么回事呢?

后来,我查了condition的await方法,它的注释如下:

Causes the current thread to wait until it is signalled or interrupted.

扫描二维码关注公众号,回复: 4284891 查看本文章

The lock associated with this Condition is atomically released and the current thread becomes disabled for thread scheduling purposes and lies dormant until one of four things happens......

原因在await方法的作用上。因为condition是通过lock创建的,而调用condition的await方法时,会自动释放和condition关联的锁。所以说,当put线程被阻塞后,它实际已经释放了锁了。所以,当有take线程想执行时,它是可以获取到锁的。

另一个问题:当等待在condition上的线程被唤醒时,因为之前调用await前,已经获取了锁,那么被唤醒时,它是自动就拥有了锁,还是需要重新获取呢?

在await方法的注释中,有如下的一段话:

In all cases, before this method can return the current thread must re-acquire the lock associated with this condition. When the thread returns it is guaranteed to hold this lock.

说明当等待在condition上的线程被唤醒时,它需要重新获取condition关联的锁,获取到之后,await方法才会返回。

猜你喜欢

转载自blog.csdn.net/li_canhui/article/details/84100166