面试官:嗯,说完ArrayBlockingQueue,再来说说LinkedBlockingQueue。Node结构,每个加入队列的元素都会被封装到下面的Node节点中,节点有指向下一个元素的指针:staticclassNode{Eitem;Nodenext;Node(Ex){item=x;}}LinkedBlockingQueue中的关键属性如下:privatefinalintcapacity;//队列容量privatefinalAtomicIntegercount=newAtomicInteger();//队列元素个数transientNodehead;//头节点privatetransientNodelast;//tailNode//入队锁privatefinalReentrantLocktakeLock=newReentrantLock();//入队等待条件对象privatefinalConditionnotEmpty=takeLock.newCondition();/入队锁privatefinalReentrantLockputLock=newReentrantLock();/入队等待条件对象privatefinalConditionnotFull=putLock.newCondition();构造函数分为指定队列长度和未指定队列长度两种。不指定时,队列的最大长度为int的最大值。当然,如果真的存储这么多元素,很可能会造成内存溢出:publicLinkedBlockingQueue(){this(Integer.MAX_VALUE);}publicLinkedBlockingQueue(intcapacity){if(capacity<=0)thrownewIllegalArgumentException();this。capacity=capacity;last=head=newNode(null);}还有一种构造方法可以在初始化时将集合作为参数传入。实现方式很容易理解,不过在循环调用后后面会提到enqueue入队方法,这里暂且略过。在LinkedBlockingQueue中,队列的头节点不存储元素,它的item为null,next指向的元素才是真正的首元素。它还有两个Condition条件对象,用于阻塞和等待。与之前的ArrayBlockingQueue不同的是,这里使用了不同的锁takeLock和putLock进行dequeue和enqueue。队列的结构是这样的:面试官:为什么要用两把锁?ArrayBlockingQueue之前用的是一把锁,是不是也能保证线程安全?Hydra:使用两把锁可以保证元素的插入和删除不互斥。剔除,使其可以同时进行,达到提高吞吐量的效果。采访者:嗯,这还是老规矩。先说insert方法是如何实现的。Hydra:这次就不提父类AbstractQueue的add方法了,反正它也调用了子类的插入方法offer,所以我们直接看offer方法的源码:publicbooleanoffer(Ee){if(e==null)thrownewNullPointerException();finalAtomicIntegercount=this.count;//队列中元素个数if(count.get()==capacity)//满returnfalse;intc=-1;Nodenode=newNode(e);finalReentrantLockputLock=this.putLock;putLock.lock();try{//并发情况,再次判断队列是否满if(count.get()=0;}在offer方法中,首先判断队列是否满。如果不满,则将元素封装成一个Node对象,并尝试获取插入锁。获取到锁后,会再次判断队列是否已满。如果满了,直接释放锁。当持有锁且队列未满时,调用入队方法。enqueue方法的实现也很简单。将当前尾节点的next指针指向新节点,再将last指向新节点:privatevoidenqueue(Nodenode){last=last.next=node;}画图,方便你要明白:入队完成后,判断队列是否已满。如果没有,则调用notFull.signal()唤醒等待将元素插入队列的线程。面试官:我记得在ArrayBlockingQueue中插入一个元素后,调用了notEmpty.signal()。为什么这里不一样?Hydra:说到这里,不得不提一下使用两个锁分别控制插入和获取元素的好处。在ArrayBlockingQueue中,使用同一个锁来控制队列的进入和退出,所以如果插入线程在插入元素后被唤醒,很有可能等待获取元素的线程不会被唤醒,造成等待时间太长了。在LinkedBlockingQueue中,分别使用队列锁putLock和队列锁takeLock,插入线程和获取线程不会互斥。因此,插入线程可以在这里不断的唤醒其他插入线程,而不用担心获取线程会不会等待太久,从而在一定程度上提高了吞吐量。当然,因为offer方法是非阻塞的,不会挂起阻塞线程,所以这里会唤醒阻塞insertedput方法的线程。面试官:那往下看,为什么c等于0的时候需要唤醒等待获取notEmpty中元素的线程呢?Hydra:其实上面获取元素的方法和插入元素的方法是同一个模式,只要有一个获取线程正在执行方法,就会不断的通过notEmpty.signal唤醒其他获取线程().只有当c等于0时,才证明前一个队列没有元素。这时候获取线程可能会阻塞,此时需要唤醒它。上面可以用一张图来说明:前面我们说过,队列中的头节点可以认为是一个不存储数据的符号节点,所以我们可以简单的认为第二个节点出队时直接取出.当然,这个过程不是很严谨,后面在说明离队过程的时候再做补充说明。面试官:那么阻塞方法put和it有什么区别呢?Hydra:put方法和offer方法的整体思路是一样的,不同的是加锁方法是可中断的,当队列中的元素满了,线程就被添加到notFull等待队列中等待,代码体现在:while(count.get()==capacity){notFull.await();}这个过程体现在上图中notFull等待队列中的元素中,不再重复说明。另外,和put方法类似,还有一个offer方法,带有waitingtime参数,可以在限定的时间内阻塞和添加。当超时到期时,插入的元素将被丢弃。我们只看offer方法不同部分的代码:publicbooleanoffer(Ee,longtimeout,TimeUnitunit){...longnanos=unit.toNanos(timeout);//转换为纳秒...while(count.get()==capacity){if(nanos<=0)returnfalse;nanos=notFull.awaitNanos(nanos);}enqueue(newNode(e));...}awaitNanos方法增加超时跳出机制在await方法的基础上,会计算循环时间内是否达到预设的超时时间。如果它在超时之前被唤醒,它将返回超时减去经过的时间。无论是被其他线程唤醒,还是到达指定超时时间后返回,只要方法返回值小于等于0,就认为超时,最后直接返回false结束。采访者:把插入解释清楚,费了好大劲。我先喝口水,你们继续说元素获取的方法。.get()==0)//队列为空returnnull;Ex=null;intc=-1;finalReentrantLocktakeLock=this.takeLock;takeLock.lock();try{if(count.get()>0){//队列不为空x=dequeue();//出队前的长队c=count.getAndDecrement();if(c>1)notEmpty.signal();}}finally{takeLock.unlock();}if(c==capacity)signalNotFull();returnx;}dequeue的逻辑和enqueue非常相似。当队列不为空时,执行dequeue进行出队操作。出队完成后,如果队列还不为空,则唤醒并等待一个线程去取队列中pending的元素。而当dequeue前的元素个数等于队列长度时,唤醒dequeue后等待队列上的加入线程。dequeue方法源码如下:privateEdequeue(){Nodeh=head;Nodefirst=h.next;h.next=h;//helpGChead=first;Ex=first.item;第一的。item=null;returnx;}前面说过,头节点head不存储数据,它的下一个节点是真正意义上的第一个节点。在出队操作中,首先获取头节点的下一个节点的首节点,将当前头节点的next指针指向自身。代码中有一个简单的注释是helpgc。这里个人的理解是减少gcCount中的引用,这样可以更早的回收。然后将新的头节点指向first,将清零前的内容返回为null。用图来表达是这样的:面试官:(看表)take方法的整体逻辑是差不多的,能简单概括一下吗?Hydra:阻塞方法take方法与poll的思想基本一致,都是阻塞获取可中断方法,当队列为空时,会挂起当前线程,加入等待条件对象队列notEmpty,等待其他线程唤醒。面试官:再给你一句话,总结一下它和ArrayBlockingQueue的异同。我要下班回家了。长度不能修改,而LinkedBlockingQueue可以指定也可以不指定长度2.存储方式不同。ArrayBlockingQueue使用数组,而LinkedBlockingQueue使用Node节点的链表。3、ArrayBlockingQueue使用锁来控制元素的插入和移除,而LinkedBlockingQueue将Enqueue锁和dequeue锁分离,提高并发性能4、ArrayBlockingQueue使用数组存储元素,插入和取出时不需要额外生成对象移动。LinkedBlockingQueue会产生新的Node节点,对gc有影响