LinkedBlockingQueue是一个线程安全的阻塞队列,它实现了BlockingQueue接口,BlockingQueue接口继承自java.util.Queue接口,并在这个接口的基础上增加了take和put方法,这两个方法正是队列操作的阻塞版本。
LinkedBlockingQueue
首先看看LinkedBlockingQueue的类图
从图中可以看出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; Nodenode = 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是基于链表的队列的阻塞版本,它实现了入队出队分离,效率比较高,而且支持容量限制。但由于它采用的是链表,所以在查找元素时效率一般。