并发容器BlockingQueue/Deque、CopyOnwrite、ConcurrentLinkedQueue扫盲

一、BlockingQueue

add----当队列满时会抛出异常              remove----当队列空时会抛出异常

offer----当队列满时会返回false            peek----当列队空时会返回NULL

put----当队列满时阻塞                         take----当队列空时阻塞

1、ArrayBlockingQueue

ArrayBlockingQueue是一个用数组实现的环形队列。在构造函数时需要传入数组大小。其核心就是一把锁加两个条件(非空和非满)。

putIndex代表现在写入的位置。

takeIndex代表读取的位置。

count代表数量。

2、LinkedBlockingQueue

LinkedBlockingQueue是一种基于单向链表的阻塞队列。因为队头和队尾是2个指针分开操作的。所以用了2把锁+2个条件。

AtomicInteger类的count。

在唤醒时需要获得对方的锁。例如,获得putLock,加入阻塞队列,count从0变为1,唤醒take线程,得先获得takeLock。

3、ArrayBlockingQueue和LinkedBlockingQueue的区别

1、ArrayBlockingQueue是基于循环数组实现的且必须指定数组大小,LinkedBlockingQueue是基于链表实现的,不指定链表长度时默认为Integer.MAX_VALUE。

2、ArrayBlockingQueue只用了一把锁+2个条件,LinkedBlockingQueue使用了两把锁+2个条件。

 ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。所以ArrayBlockingQueue只用一把锁可能基于性能已经足够优秀,使用两把锁不会带来很大的优化,反而会给代码带来额外的复杂性。

4、PriorityBlockingQueue

按照元素的优先级从小到大出队列的。使用数组实现的小跟堆只有1把锁+1个条件,没有非满条件,默认初始大小为11,元素超过这个大小之后会自动扩容

5、DelayQueue

延迟队列,其实就是一个按照延迟时间大小出队的PriorityQueue。放入DelayQueue中的元素必须实现Delayed接口,重写getDelay方法。

(1)getDelay方法

如果getDelay方法的返回值小于等于0,表示该元素到期,需要从队列中拿出来执行。

队列中的元素按照getDelay的值进行排序。

(2)take方法

调用take函数时,如果队列为空或者堆顶元素的延迟时间没到也会阻塞。

使用Thread leader变量记录了等待堆顶元素的第一个线程,通过getDelay可以知道阻塞多长时间,当其他线程发现有leader在等待时会无限期阻塞等待leader拿走后唤醒。

(3)put方法

如果放进去的元素刚好在堆顶,需要将leader设为null,通知等待的线程来任务了,更新leader已经等待时间。如果不在堆顶不需要通知,没有影响。

6、SynchronousQueue

是一种特殊的BlockingQueue,它本身没有容量。先调用put,线程会阻塞,知道另外一个线程调用了take,两个线程才同时解锁,反之亦然。

使用一个TransferQueue的一个单向链表实现,有公平和非公平两种模式。公平模式类似于队列先进先出,非公平模式类似于栈,先进后出。

二、BlockingDeque

定义了一个阻塞的双端队列接口,只有一个实现,LinkedBlockingDeque

和LinkedBlockingQueue基本一样,区别仅在于LinkedBlockingDeque使用的是双向链表。

三、CopyOnWrite

CopyOnWrite指在写的时候不是直接写源数据,而是把数据拷贝一份进行修改。再通过悲观锁或者乐观锁的方式写回。在读的时候不加锁。

四、ConcurrentLinkedQueue/Deque

首先它是一个单向链表

在ConcurrentLinkedQueue中,head/tail的更新可能落后于节点的入队和出队,因为它不是直接对head/tail指针进行CAS操作的,而是对Node中item进行操作。

(1)入队列

即使tail指针没有移动,只要对p的next的指针成功进行CAS操作,就算入队列。

每连续追加2个节点,才后移一次tail指针。即使CAS失败也没关系,可以由下1个线程来移动tail指针。

(2)出队列

出队列的判断并不是观察tail指针的位置,而是依赖于head指针后续的节点是否为NULL这一条件。

只要对节点的item执行CAS操作,置为NULL成功则出队列成功,即使head指针没有成功移动也可以由下一个线程继续完成。

(3)队列判空

从head指针开始往后找到第一个不为NULL的节点,如果找到,则队列不为空;如果找不到,则队列为空。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值