LinkedBlockingQueue

阅读更多

LinkedBlockingQueue是一个线程安全的阻塞队列,它实现了BlockingQueue接口,BlockingQueue接口继承自java.util.Queue接口,并在这个接口的基础上增加了take和put方法,这两个方法正是队列操作的阻塞版本。

 

LinkedBlockingQueue

首先看看LinkedBlockingQueue的类图

LinkedBlockingQueue_第1张图片

从图中可以看出LinkedBlockingQueue中引入了两把锁takeLock和putLock,显然分别是用于take操作和put操作的。既LinkedBlockingQueue入队和出队用的是不同的锁,那么LinkedBlockingQueue可以同时进行入队和出队操作。

同时,还引入了两个条件变量,notEmpty和notFull,参照生产者消费者模型,当队列不空的时候消费者可以消费,当队列不满的时候,生产者可以生产。所以notEmpty是告诉程序现在队列中有元素,可以执行take操作;notFull告诉程序队列还没满,还可以往里面放。

类图中还有一个count字段,是记录队列中结点个数的,因此对于LinkedBlockingQueue来说,获取队列的size时间复杂度为o(1),capacity为队列的容量,head和last指针分别指向队头和队尾元素。

 

入队操作

    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();//1
        int c = -1;
        Node node = new Node(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            while (count.get() == capacity) {//2
                notFull.await();
            }
            enqueue(node);//3
            c = count.getAndIncrement();//4
            if (c + 1 < capacity)
                notFull.signal();//5
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();//6
    }

1、首先检查需要入队的元素是否为空,若为空抛出InterruptedException异常(1处所示);

2、然后获取putLock锁,判断队列是否已满,如果已满就挂起当前线程,直到notFull这个条件满足(2处所示);

3、到此表明队列未满,可以入队,和普通入队操作一样(3所示);

4、入队后将队列count+1,count采用的是AtomicInteger的原子操作(4所示);

5、再次判断是否已满,如果未满就通知其他线程(5所示)

6、执行完以上步骤后就解putLock锁。c默认为-1,加入队列后结果为0,说明之前队列为空。由于此时锁还在当前线程上,所以不可能出队,也不可能入队,那么在队列不为空的时候就需要安全的唤醒一个出队线程了。

以上就是入队操作的全过程,LinkedBlockingQueue还有非阻塞版本的入队操作:步骤和put操作类似,但也有一些区别:

public boolean add(E e) 在入队不成功的时候会抛出异常;

public boolean offer(E e)不会抛出异常,而是直接返回true or false

 

offer(E e,  long timeout,TimeUnit unit) throws InterruptedException超时版本的offer也是返回true or false。如果在指定时间范围内没有入队成功会阻塞,直到超时抛出异常。

 

出队操作

    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();//1
            }
            x = dequeue();//2
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();//3
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();//4
        return x;
    }

 

1、首先获取takeLock锁,然后判断队列中是否有元素,如果没有元素就阻塞,直到notEmpty条件出现(1所示)

2、然后出队(2所示)

3、出队之后判断队列中是否还有元素,如果还有就发出notEmpty通知其他线程(3所示)

4、执行以上步骤后,表明成功出队,释放takeLock锁。然后发出队列不空的信号。

和入队一样,出队操作也有非阻塞版本,与入队操作一一对应,这里不作分析。

 

总结

LinkedBlockingQueue是基于链表的队列的阻塞版本,它实现了入队出队分离,效率比较高,而且支持容量限制。但由于它采用的是链表,所以在查找元素时效率一般。

 

你可能感兴趣的:(链表,阻塞,生产者,消费者)