LinkedBlockingQueue源码解析(1)
此文已由作者赵计刚授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
1、对于LinkedBlockingQueue需要掌握以下几点
创建
入队(添加元素)
出队(删除元素)
2、创建
Node节点内部类与LinkedBlockingQueue的一些属性
static class Node<E> { E item;//节点封装的数据 /** * One of: * - the real successor Node * - this Node, meaning the successor is head.next * - null, meaning there is no successor (this is the last node) */ Node<E> next;//下一个节点 Node(E x) { item = x; } } /** 指定链表容量 */ private final int capacity; /** 当前的元素个数 */ private final AtomicInteger count = new AtomicInteger(0); /** 链表头节点 */ private transient Node<E> head; /** 链表尾节点 */ private transient Node<E> last; /** 出队锁 */ private final ReentrantLock takeLock = new ReentrantLock(); /** 出队等待条件 */ private final Condition notEmpty = takeLock.newCondition(); /** 入队锁 */ private final ReentrantLock putLock = new ReentrantLock(); /** 入队等待条件 */ private final Condition notFull = putLock.newCondition();
2.1、public LinkedBlockingQueue(int capacity)
使用方法:
Queue<String> abq = new LinkedBlockingQueue<String>(1000);
源代码:
/** * 创建一个 LinkedBlockingQueue,容量为指定容量 */ public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node<E>(null);//初始化头节点和尾节点,均为封装了null数据的节点 }
注意点:
LinkedBlockingQueue的组成一个链表+两把锁+两个条件
2.2、public LinkedBlockingQueue()
使用方法:
Queue<String> abq = new LinkedBlockingQueue<String>();
源代码:
/** * 创建一个LinkedBlockingQueue,容量为整数最大值 */ public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }
注意点:默认容量为整数最大值,可以看做没有容量限制
3、入队:
3.1、public boolean offer(E e)
原理:
在队尾插入一个元素, 如果队列没满,立即返回true; 如果队列满了,立即返回false
使用方法:
abq.offer("hello1");
源代码:
/** * 在队尾插入一个元素, 容量没满,可以立即插入,返回true; 队列满了,直接返回false * 注:如果使用了限制了容量的队列,这个方法比add()好,因为add()插入失败就会抛出异常 */ public boolean offer(E e) { if (e == null) throw new NullPointerException(); final AtomicInteger count = this.count;// 获取队列中的元素个数 if (count.get() == capacity)// 队列满了 return false; int c = -1; final ReentrantLock putLock = this.putLock; putLock.lock();// 获取入队锁 try { if (count.get() < capacity) {// 容量没满 enqueue(e);// 入队 c = count.getAndIncrement();// 容量+1,返回旧值(注意) if (c + 1 < capacity)// 如果添加元素后的容量,还小于指定容量(说明在插入当前元素后,至少还可以再插一个元素) notFull.signal();// 唤醒等待notFull条件的其中一个线程 } } finally { putLock.unlock();// 释放入队锁 } if (c == 0)// 如果c==0,这是什么情况?一开始如果是个空队列,就会是这样的值,要注意的是,上边的c返回的是旧值 signalNotEmpty(); return c >= 0; }
/** * 创建一个节点,并加入链表尾部 * @param x */ private void enqueue(E x) { /* * 封装新节点,并赋给当前的最后一个节点的下一个节点,然后在将这个节点设为最后一个节点 */ last = last.next = new Node<E>(x); }
private void signalNotEmpty() { final ReentrantLock takeLock = this.takeLock; takeLock.lock();//获取出队锁 try { notEmpty.signal();//唤醒等待notEmpty条件的线程中的一个 } finally { takeLock.unlock();//释放出队锁 } }
如果,入队逻辑不懂,查看最后总结部分入队逻辑的图,代码非常简单,流程看注释即可。
3.2、public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException
原理:
在队尾插入一个元素,,如果队列已满,则进入等待,直到出现以下三种情况:
被唤醒
等待时间超时
当前线程被中断
使用方法:
try { abq.offer("hello2",1000,TimeUnit.MILLISECONDS); } catch (InterruptedException e) { e.printStackTrace(); }
源代码:
/** * 在队尾插入一个元素,,如果队列已满,则进入等待,直到出现以下三种情况: * 1、被唤醒 * 2、等待时间超时 * 3、当前线程被中断 */ public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { if (e == null) throw new NullPointerException(); long nanos = unit.toNanos(timeout);// 转换为纳秒 int c = -1; final ReentrantLock putLock = this.putLock;// 入队锁 final AtomicInteger count = this.count;// 总数量 putLock.lockInterruptibly(); try { while (count.get() == capacity) {// 容量已满 if (nanos <= 0)// 已经超时 return false; /* * 进行等待: 在这个过程中可能发生三件事: * 1、被唤醒-->继续当前这个while循环 * 2、超时-->继续当前这个while循环 * 3、被中断-->抛出中断异常InterruptedException */ nanos = notFull.awaitNanos(nanos); } enqueue(e);// 入队 c = count.getAndIncrement();// 入队元素数量+1 if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); return true; }
注意:
awaitNanos(nanos)是AQS中的一个方法,这里就不详细说了,有兴趣的自己去查看AQS的源代码。
免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 LESS+to+MCSS
【推荐】 BRVAH(让RecyclerView变得更高效) (2)
【推荐】 微服务化之缓存的设计