面试准备 --- Java并发队列原理

面试准备,无法保证理解正确性,慎,欢迎纠正

前言

并发队列与普通队列的区别的确就在于并发二字,而并发的的基础就是线程安全,如何实现线程安全是我们最重要的需要理解的部分.线程安全的保证无非就是可见性和原子性(有序性一般不说).接下来的各种队列主要就这两点的实现来说.

非阻塞队列

非阻塞队列即不使用锁的队列,主要使用CAS操作保证原子性

ConcurrentLinkedQueue

ConcurrentLinkedQueue是无界非阻塞队列.底层数据结构是单向链表,通过volatile分别修饰两个节点,这两个节点分别存放链表的头结点和尾节点来实现可见性,通过CAS操作保证节点入队出队时操作链表的原子性.

  • offer():在队列尾部添加一个元素

    • 多线程情况下,如何实现多线程同时插入元素.前面说了,通过CAS实现入队时操作的原子性,看一下源码这一行if (p.casNext(null, newNode)),如果多线程同时执行到这一步,因为CAS操作casNext本身是原子的,如果有一个线程完成了操作,那么其他竞争的线程会重新进入这一行代码的上层循环尝试进行CAS操作,只有成功才会返回.
    • 这个过程是无锁的,因为没有线程因为没有完成操作而被挂起阻塞,而是在无限循环中不断尝试,就是利用CPU资源换取阻塞引起的开销.孰是孰非,要具体分析.
  • poll() : 从队头移除一个元素

    • 还是一行代码:if (item != null && p.casItem(item, null)),可以看到,所谓的删除操作就是将当前节点的值设为null,然后重新指定头结点,被移除的节点没了引用,会在gc时被回收,因为整个队列维持了头结点和尾节点两个volatile变量,所以poll和offer并不冲突.
    • 有一点需要注意,如果没有执行过offer操作就直接poll,会返回null.
  • peek():获取头结点元素,不移除

    • 这个操作其实和poll类似,不过没有cas操作
  • size() ; 获取队列长度

    • 这个方法有个问题,因为没有加锁,所以如果在调用size()的过程中可能发生增删的操作,造成统计不准确.

阻塞队列

LinkedBlockingQueue

使用ReentrantLock实现锁机制,底层也是单向列表,也有两个节点存放头节点和尾节点,还有一个count代表元素个数.

  • 类中有两个ReentrantLock,分别用于添加和删除操作时的锁控制.LinkedBlockingQueue是一个有界的阻塞队列,可以初始指定容量,默认是0x7fffffff;

  • offer:队列尾部添加一个元素,队列已满返回false,方法不阻塞.

    • offer操作的过程是这样的:先判断元素是否为空,为空抛出空指针异常;然后判断队列是否已满,如果已满返回false;构造新节点,获取putLock独占锁,再一次判断队列是否满,不满则入队列,计数+1,最后释放锁.了解了独占锁,这些其实很简单也很正常,解释一下加粗的内容,为什么要重新判断队列是否满.第一次判断队列是否满时还没有拿到独占锁,如果没有拿到独占锁而被挂起,后来再拿到锁时,可能已经有其他线程进入添加了元素,所以要重新判断.
  • put() ; 类似offer,不过如果队列已满不会返回false,而是阻塞线程,直到队列空闲再插入

    • 具体实现还有一点需要注意,就是队列已满的等待和没有获取到锁的等待是不同的,前者,会将阻塞线程放到条件变量的条件队列中,后者则是放在AQS的阻塞队列中.具体看ReentrantLock源码那篇
  • poll:从头部移除一个元素,如果队列为空返回null,该方法不阻塞

    • 其实实现和offer差不多,不过对应逻辑变一变.注意的是也要判断两次队列是否为空,道理一样.
  • peek():获取头部元素但不删除,队列为空返回null.

  • take():类似poll,不过队列为空阻塞线程直到队列不为空.

  • 方法是不是阻塞的,就是当队列满或空时是否阻塞线程.如果被阻塞的线程被其他线程调用了中断方法,会抛出InterruptedException异常而返回.

  • offer和put操作成功后,会通知被take操作阻塞的线程,类似的,take和poll操作成功后也会通知被put操作阻塞的线程.

ArrayBlockingQueue

底层通过数组实现的有界队列.维持两个下标,一个入队下标,一个出队下标.因为是数组,所以只使用一个独占锁,也就意味着同时只有一个线程进行入队和出队操作.

  • offer() ; 向队尾插入一个元素,如果队列有空闲则插入成功并返回true ; 如果队列已满则丢弃当前元素放回false,如果插入元素为nul返回空指针异常.
    • 如果添加成功,会通知一个被take操作阻塞的线程.put同
  • put(); 操作,向队尾插入一个元素,如果队列有空闲则插入成功后直接返回true,如果队列已满则阻塞线程知道队列有空闲并插入成功并返回true;
  • poll():从头部移除一个元素,如果队列为空返回null;
    • 所谓移除,就是重置对头元素,重设对头下标
    • 移除后会激活条件变量通知条件队列中因为队列满而被阻塞的线程.take同
  • take();从头部移除一个元素,如果队列空,则阻塞线程.

PriorityBlockingQueue

待续

猜你喜欢

转载自blog.csdn.net/qq_36865108/article/details/86702298