JDK源码——java.util.concurrent(八)
来源:互联网 发布:淘宝客链接生成二维码 编辑:程序博客网 时间:2024/05/21 09:51
测试代码:
https://github.com/kevindai007/springboot_houseSearch/tree/master/src/test/java/com/kevindai/juc
LinkedBlockingQueue
上章说到了ArrayBlockingQueue,现在来看看LinkedBlockingQueue.LinkedBlockingQueue也是一个阻塞的有界队列,其用法与ArrayBlockingQueue基本一致,只不过内部实现不一样,这里不再写demo咱们直接来看看起源码.首先咱们看看其关键属性和构造方法
//node节点,只有一个next,所以是单向链表static class Node<E> { //存储节点元素 E item; //指向下一节点 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;//take, poll等取出方法的锁private final ReentrantLock takeLock = new ReentrantLock();//取出元素方法等待条件private final Condition notEmpty = takeLock.newCondition();//put, offer等添加方法的锁private final ReentrantLock putLock = new ReentrantLock();//添加元素方法等待条件private final Condition notFull = putLock.newCondition();public LinkedBlockingQueue() { this(Integer.MAX_VALUE);}public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; //头结点、尾节点都是Null last = head = new Node<E>(null); }
咱们先来看看其add()方法
//add方法定义与于父类AbastactQueue中,实际上是调用实现类中的offer()方法 public boolean add(E e) { if (offer(e)) return true; else throw new IllegalStateException("Queue full"); } 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; //构造节点 Node<E> node = new Node(e); //加锁 final ReentrantLock putLock = this.putLock; putLock.lock(); try { //如果未满,则入列,元素数量+1 if (count.get() < capacity) { enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) //唤醒在不为满(即put())条件等待的线程 notFull.signal(); } } finally { putLock.unlock(); //这里c默认是-1,经过enqueue入队,c=count.getAndIncrement(),如果变成0,说明链表队列原来是空的,现在有元素了 if (c == 0) //唤醒在不为满(即take())条件等待的线程 signalNotEmpty(); return c >= 0; } private void enqueue(Node<E> node) { //当前节点加到链中,并置为尾节点 last = last.next = node; } private void signalNotEmpty() { final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { notEmpty.signal(); } finally { takeLock.unlock(); } }
与ArrayBlockingQueue一样,offer()方法并不阻塞.
再来看看put()和take()方法
public void put(E e) throws InterruptedException { //元素为Null抛出异常 if (e == null) throw new NullPointerException(); int c = -1; //构造节点 Node<E> node = new Node(e); final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; //可中断 putLock.lockInterruptibly(); try { //如果队列已满,则阻塞等待 while (count.get() == capacity) { notFull.await(); } //被唤醒或队列未满,则元素入列,更改元素数量 enqueue(node); c = count.getAndIncrement(); //队列未满,则唤醒所有put()上等待的队列 if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) //唤醒在不为满(即take())条件等待的线程 signalNotEmpty(); }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(); } //此时队列不为空,则获取第一个节点 x = dequeue(); //元素数量减一 c = count.getAndDecrement(); if (c > 1) //如果还有元素,则唤醒在take()方法上等待的线程 notEmpty.signal(); } finally { takeLock.unlock(); } //默认c为-1,经过take后c=count.getAndDecrement();说明原来队列是满的,take后不满,就可以唤醒notFull条件队列 if (c == capacity) signalNotFull(); return x; } private E dequeue() { //获取头节点 Node<E> h = head; //获取第一个节点 Node<E> first = h.next; h.next = h; // help GC head = first; E x = first.item; first.item = null; return x; } private void signalNotFull() { final ReentrantLock putLock = this.putLock; putLock.lock(); try { notFull.signal(); } finally { putLock.unlock(); } }
再来看看remove()
//因为remove操作需要遍历整个链表,所以加2把锁遍历public boolean remove(Object o) { if (o == null) return false; fullyLock(); try { for (Node<E> trail = head, p = trail.next; p != null; trail = p, p = p.next) { if (o.equals(p.item)) { unlink(p, trail); return true; } } return false; } finally { fullyUnlock(); } } void fullyLock() { putLock.lock(); takeLock.lock(); } void fullyUnlock() { takeLock.unlock(); putLock.unlock(); } void unlink(Node<E> p, Node<E> trail) { p.item = null; trail.next = p.next; if (last == p) last = trail; if (count.getAndDecrement() == capacity) notFull.signal(); }
这个类很简单,相信大家也是要看就懂,在此不做过多分析,开始学习下一个
ConcurrentLinkedQueue
ConcurrentLinkedQueue是一个非阻塞的无界的线程安全队列(基于cas实现),下面看看其是如何实现的
private static class Node<E> { //存放元素 volatile E item; //指向下一元素 volatile Node<E> next; //构建node节点 Node(E item) { UNSAFE.putObject(this, itemOffset, item); } //cas更新Item boolean casItem(E cmp, E val) { return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val); } void lazySetNext(Node<E> val) { UNSAFE.putOrderedObject(this, nextOffset, val); } //cas跟新next节点 boolean casNext(Node<E> cmp, Node<E> val) { return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); } private static final sun.misc.Unsafe UNSAFE; private static final long itemOffset; private static final long nextOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class k = Node.class; itemOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("item")); nextOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("next")); } catch (Exception e) { throw new Error(e); } } } //头节点 private transient volatile Node<E> head; //尾节点 private transient volatile Node<E> tail; public ConcurrentLinkedQueue() { head = tail = new Node<E>(null); }
可以看到ConcurrentBlockingQueue并没有容量等相关属性,因此是一个无界队列
下面咱们看看其入列方法
public boolean add(E e) { return offer(e); } public boolean offer(E e) { checkNotNull(e); //构建节点 final Node<E> newNode = new Node<E>(e); //t为tail节点,p为尾节点,默认相等,采用自旋+cas方式,直到入队成功 for (Node<E> t = tail, p = t;;) { //获得p的下一个节点 Node<E> q = p.next; // 如果下一个节点是null,即p节点就是尾节点 if (q == null) { //将入队节点newNode设置为当前队列尾节点p的next节点 if (p.casNext(null, newNode)) { // 如果p.casNext有个线程成功了,p=newNode // 比较 t (tail) 是不是 最后一个节点 if (p != t) // 如果不等,就利用cas将,尾节点移到最后 // 如果失败了,那么说明有其他线程已经把tail移动过,也是OK的 casTail(t, newNode); return true; } } else if (p == q) // 有可能刚好插入一个,然后P 就被删除了,那么 p==q // 这时候在头结点需要从新定位。 p = (t != (t = tail)) ? t : head; else //p有next节点,表示p的next节点是尾节点,则需要重新更新p后将它指向next节点 p = (p != t && t != (t = tail)) ? t : q; } }
从源代码我们看出入队过程中主要做了三件事情
- 定位出尾节点
- 使用CAS+自旋指令将入队节点设置成尾节点的next节点
- 重新定位tail节点
再看看出列方法
public E poll() { // 设置起始点 restartFromHead: for (;;) { for (Node<E> h = head, p = h, q;;) { E item = p.item; // 利用cas 将第一个节点,设置未null if (item != null && p.casItem(item, null)) { // 和上面类似,p的next被删了, // 然后然后判断一下,目的为了保证head的next不为空 if (p != h) // hop two nodes at a time updateHead(h, ((q = p.next) != null) ? q : p); return item; } else if ((q = p.next) == null) { // 有可能已经被另外线程先删除了下一个节点 // 那么需要先设定head 的位置,并返回null updateHead(h, p); return null; } else if (p == q) // 这个一般是删完了(有点模糊) continue restartFromHead; else // 和offer 类似,这历使用保证下一个节点有值,才能删除 p = q; } } }
首先获取head节点的元素,并判断head节点元素是否为空,如果为空,表示另外一个线程已经进行了一次出队操作将该节点的元素取走;如果不为空,则使用CAS的方式将head节点的引用设置成null,如果CAS成功,则直接返回head节点的元素;如果CAS不成功,表示另外一个线程已经进行了一次出队操作更新了head节点,导致元素发生了变化,需要重新获取head节点。如果p节点的下一个节点为null,则说明这个队列为空(此时队列没有元素,只有一个伪结点p),则更新head节点。
再来看看size()方法
public int size() { int count = 0; //这里会遍历所有节点,效率较低 for (Node<E> p = first(); p != null; p = succ(p)) if (p.item != null){ if (++count == Integer.MAX_VALUE) break; } return count; }
size()方法会遍历所有节点,效率较低,因此如果想判断队列是否为空是可考虑使用empty()
public boolean isEmpty() { return first() == null; }
- JDK源码——java.util.concurrent(八)
- JDK源码——java.util.concurrent
- JDK源码——java.util.concurrent(二)
- JDK源码——java.util.concurrent(三)
- JDK源码——java.util.concurrent(四)
- JDK源码——java.util.concurrent(五)
- JDK源码——java.util.concurrent(六)
- JDK源码——java.util.concurrent(七)
- JDK源码(FutureTask)——java.util.concurrent(十)
- JDK源码阅读 java.util.concurrent—并发容器类
- Jdk源码阅读之Java.util.concurrent
- JDK源码(线程池ThreadPoolExecutor)——java.util.concurrent(九)
- JDK源码阅读 java.util.concurrent.Executors相关分析
- java.util.concurrent解析——FutureTask源码解析
- java.util.concurrent解析——ThreadPoolExecutor源码解析
- JDK源码——java.util
- java.util.concurrent 源码阅读
- java.util.concurrent——TimeUnit
- day22-实战5
- Vue.nextTick()函数
- CentOS 7 安装composer
- Windows消息机制(MFC)
- eclipse配置maven
- JDK源码——java.util.concurrent(八)
- ”想哭“病毒
- MM 标准的进销存报表
- 解决vue渲染时闪烁{{}}的问题
- Maven依赖中的scope详解
- 质因数分解——洛谷 P1069 细胞分裂
- Pandas 索引(index)/选取(select)/标签(label)操作
- Eclipse+Maven创建webapp项目
- DNS服务器之一:DNS简介及BIND安装与基本配置