在上一遍线程池的讲解中,如果线程的大小大于核心线程的大小,就会放到缓冲队列里面,这个队列就是LinkedBlockingDeque。下面就来深入讲解一下这个队列。java源码系列。
一.LinkedBlockingDequ的定义
首先看下图谱
它是一个队列,而且还是阻塞式的,还可以告诉你,它是线程安全的。LinkedBlockingDeque用的是一个双向的链表,Node的声明如下:
static final class Node<E> { E item; //当前的值 Node<E> prev; //指向前驱节点 Node<E> next; //指向后继节点 Node(E x) { item = x; } }
使用两个条件来实现阻塞
//从队列中拿取值的条件判断,如果队列是空的话,有线程再来拿值的话,这个线程要被阻塞,直到有值被添加了,值才能被别人拿走 private final Condition notEmpty = lock.newCondition(); //往队列里面添加值,队列满了了,需要阻塞,等到别的线程把队列里面的值拿走才可以添加值 private final Condition notFull = lock.newCondition();
这种阻塞就是典型的生产者--消费者模式,比如去听老教授讲课,教室里面的座位就只有那么多,座位坐满了,里面来的学生就需要站着,如果有坐着的学生离开座位,接下来旁边的同学就可以坐下。
LinkedBlockingDeque锁使用的独占锁ReentrantLock,这个锁就不细讲了。知道他是独占,别的线程等待就可以了。
二.LinkedBlockingDeque的显现
2.1 初始化
public LinkedBlockingDeque() { //无界? 不指定最大是int的最大值 this(Integer.MAX_VALUE); }
public LinkedBlockingDeque(int capacity) { //指定队列的大小,好处是可以防止过度扩张 if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; }
public LinkedBlockingDeque(Collection<? extends E> c) { this(Integer.MAX_VALUE); final ReentrantLock lock = this.lock; lock.lock(); //获取到锁 try { for (E e : c) { if (e == null) throw new NullPointerException(); if (!linkLast(new Node<E>(e))) //将集合里面的值一个一个的添加进去,如果队列满了,就会抛出Deque full的异常 throw new IllegalStateException("Deque full"); } } finally { lock.unlock(); } }
2.2 添加队列值
public void put(E e) throws InterruptedException { putLast(e); }
public void putLast(E e) throws InterruptedException { if (e == null) throw new NullPointerException(); //如果添加的数据为空,抛出空指针异常 Node<E> node = new Node<E>(e); //初始数据节点 final ReentrantLock lock = this.lock; lock.lock(); //上锁 try { while (!linkLast(node)) //将节点放到队列的队尾 notFull.await(); /如果队列满了的话,需要阻塞 } finally { lock.unlock(); } }
private boolean linkLast(Node<E> node) { // assert lock.isHeldByCurrentThread(); if (count >= capacity) //超出了容量,需要阻塞 return false; Node<E> l = last; //记录last节点 node.prev = l; //当前node的前驱指向last last = node; last指向node if (first == null) //如果第一个为空,node就是第一个 first = node; else l.next = node; //最前一个last的next指针指向现在的node ++count; //队列里面的值加 1 notEmpty.signal(); //释放资源,其他线程可以添加值 return true; }2.3 获取队列值
public E take() throws InterruptedException { return takeFirst(); }
public E takeFirst() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lock(); try { E x; while ( (x = unlinkFirst()) == null) //获取队列的值,如果第一个值为null,需要等其他线程把值添加进去,它才能获取到值 notEmpty.await(); return x; } finally { lock.unlock(); } }
private E unlinkFirst() { //出队列,移除第一个值 // assert lock.isHeldByCurrentThread(); Node<E> f = first; if (f == null) //如果第一个值为空 return null; Node<E> n = f.next; //n 指向第一个节点的下一个 E item = f.item; //拿到第一个节点里面的值 f.item = null; //将第一个节点里面的值赋值为空,方便gc f.next = f; //之前第一个节点现在后继节点已经没有了,指向了自身,没有别的节点有对它的引用,会被gc线程清除 first = n; //现在第一个节点指向了之前第二个节点n if (n == null) //如果第二个节点是null last = null; //尾指针指向null else n.prev = null; //否则的话,之前第二个的节点的前驱指针指向null --count; //队列的数据减一 notFull.signal(); //之前获取数据没有获取成功的线程现在可以拿取数据了 return item; }
三.总结
看完了LinkedBlockingDeque的源码实现是不是感觉很简单。它首先是一个双向链表,可以支持先进先出。其次线程安全,用的是ReentranLock。接着是阻塞式的,用的是notFull,notEmptyl两个Condition实现。
纸上得来终觉浅,绝知此事要躬行。