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;        }    }

从源代码我们看出入队过程中主要做了三件事情

  1. 定位出尾节点
  2. 使用CAS+自旋指令将入队节点设置成尾节点的next节点
  3. 重新定位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;    }
原创粉丝点击