【笔记】Java并发编程之美-Java并发包中并发队列原理剖析

ConcurrentLinkedQueue原理探究

线程安全的无界非阻塞队列,底层数据结构使用单向链表,出入队使用CAS实现线程安全。

类图结构

两个volatile类型Node节点存放队列首尾节点。默认头、尾指向item为null的哨兵节点。Node节点内部维护一个使用volatile修饰的变量item,存放节点的值。
在这里插入图片描述

ConcurrentLinkedQueue原理介绍

1.offer操作
队列末尾添加元素,若为null抛出NullPointerException异常,否则由于ConcurrentLinkedQueue是无界队列,方法一直返回true。另外,由于使用CAS无阻塞算法,所以不会引起阻塞。
offer操作中关键步骤是通过原子CAS操作来控制某时只有一个线程可以追加元素到队列末尾。进行CAS竞争失败的线程会反复循环尝试,也就是通过无限循环不断进行CAS操作尝试来代替阻塞算法挂起调用线程。
2.add操作
链表末尾添加元素,其实在内部就是offer。
3.poll操作
队列头部获取并移除一个元素,队列为空返回null。
poll方法移除元素时,只是简单使用CAS操作把当前item值设为null,然后重新设置头节点将该元素从队列移除等待作为孤立节点被回收。另外,如果在执行分支中发现头节点被修改了,要跳到外层循环重新获取头节点。
4.peek操作
获取队列头部一个元素(只获取不移除),队列为空返回null。
第一次调用peek操作时,会删除哨兵节点,并让队列head节点指向队列里面第一个元素或null。
5.size操作
计算当前队列元素个数,并发环境下不精确因为没有加锁。
6.remove操作
队列里面存在该元素则删除之,存在多个删除第一个,返回true;否则false。
7.contains操作
判断队列里是否含有指定对象,有则返回true并。发环境下不精确因为没有加锁。

在这里插入图片描述

LinkedBlockingQueue原理探究

单向链表,独占锁实现。默认队列容量为 0x7fffffff,用户也可以自己指定容量,所以从一定程度上可以说 LinkedBlockingQueue 是有界阻塞队列。

类图结构

在这里插入图片描述
单向链表实现,有两个Node分别存放首尾节点,有一个初始值为0的原子变量count,记录队列元素个数。两个ReentrantLock的实例,分别控制元素入队和出队的原子性,其中takeLock用来控制同时只有一个线程可以从队列头获取元素,其他线程等待;putLock控制同时只能有一个线程获取锁在队列尾添加元素。

LinkedBlockingQueue原理介绍

1.offer操作
队列尾部添加元素,队列有空闲插入成功返回true;队列已满则丢弃当前元素然后返回false。该方法是非阻塞的。
2.put操作
队列尾部添加元素,队列有空闲插入成功返回true;队列已满则阻塞当前线程,直到队列有空。若阻塞时被其他线程设置中断标志,则被阻塞线程抛出InterruptedException异常返回。
3.poll操作
从队列头部获取并移除一个元素,队列为空则返回null,该方法不阻塞。
4.peek操作
获取队列头部元素但不从队列里面移除它,如果队列为空返回null。该方法不阻塞。
5.take操作
获取队列头部元素并移除它,队列为空则阻塞当前线程直到队列不为空然后返回元素。若阻塞时被其他线程设置了中断标志,则被阻塞线程抛出InterruptedException异常返回。
6.remove操作
删除队列里面指定的元素,有则删除并返回true,无则返回false。
7.size操作
获取当前队列元素个数。
return count.get()
由于出入队的count加了锁,所以相比ConcurrentLinkedQueue的size要准确。ConcurrentLinkedQueue不使用原子变量而是遍历链表获取size的原因:使用原子变量保存队列元素个数需要入队、出队保证原子性,而ConcurrentLinkedQueue采用的是CAS无锁算法,做不到。
在这里插入图片描述

ArrayBlockingQueue原理探究

有界数组实现。

类图结构

在这里插入图片描述
一个数组items存放元素,putIndex表示入队下标,takeIndex表示出队下标,count统计队列元素个数。定义可知,其都没有volatile修饰,因为访问其都是在锁块内。独占锁lock用来保证出、入队操作的原子性,这保证了同时只有一个线程可以入队、出队。

ArrayBlockingQueue原理介绍

1.offer操作
队尾插入元素,成功返回true,队列已满则丢弃当前元素返回然后返回false。e元素为null则抛出NullPointerException异常。该方法不阻塞。
2.put操作
队尾插入元素,队列有空闲则插入后直接返回true,队列已满则阻塞直到有空闲插入后返回true。若阻塞时被其他线程设置了中断标志,则被阻塞线程抛出InterruptedException异常而返回。若e元素为null则抛出NullPointerException异常。
3.poll操作
队列头部获取并移除一个元素,如果队列为空则返回null。该方法不阻塞。
4.take操作
队列头部获取并移除一个元素,队列为空阻塞当前线程直到队列不为空然后返回元素。若阻塞时被其他线程设置了中断标志,则被阻塞线程抛出InterruptedException异常而返回。
5.peek操作
获取队列头部元素但不移除它,如果队列为空则返回null。该方法不阻塞。
6.size操作
计算队列元素个数。精确的,因为加了全局锁。
因为count没有声明为volatile,所以此处访问要加锁。

在这里插入图片描述

PriorityBlockingQueue原理探究

带优先级的无界阻塞队列,每次出队都返回优先级最高/低的元素。内部使用平衡二叉树堆实现,所以直接遍历队列元素不保证有序。默认使用对象的compareTo方法提供比较规则,如果需要自定义比较规则可以自定义comparators。

类图结构

在这里插入图片描述

PriorityBlockingQueue原理介绍

1.offer操作
插入元素。(注意扩容和建堆算法)。
2.poll操作
获取队列内部堆树根节点元素,若队列为空,返回null。
3.put操作
内部就是调用的offer操作。
4.take操作
获取队列内部堆树根节点元素,若队列为空则阻塞。
5.size操作
计算队列元素个数。加了锁。
在这里插入图片描述

DelayQueue原理探究

无界阻塞延迟队列,队列中每个元素有个过期时间,从队列获取元素时,只有过期元素才会出队列。队列头元素是最快要过期的元素。

类图结构

在这里插入图片描述
在如下代码中,条件变量 available 与 lock 锁是对应的,其目的是为了实现线程问同步。
private final Condition available= lock.newCondition() ;
其中 leader 变量的使用基于 Leader-Follower 模式的变体,用于尽量减少不必要的线程等待。当一个线程调用队列的 take 方法变为 leader 线程后,它会调用条件变量 available. awaitNanos(delay) 等待 delay 时间,但是其他线程 (follwer 线程) 则会调用 available.await()进行无限等待。 leader 线程延迟时间过期后,会退出 take 方法, 并通过调用
available.signal()方法唤醒一个 follwer线程,被唤醒的 follwer线程被选举为新的 leader线程。

DelayQueue原理介绍

1.offer操作
插入元素。要实现Delayed接口。
2.take操作
获取并移除队列里延迟时间过期的元素,如果队列里面没有过期元素则等待。
3.poll操作
获取并移除队头过期元素,若没有过期元素返回null。
4.size操作
计算队列元素个数,包含过期和未过期的。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值