JUC(九)
创始人
2024-06-03 11:50:42

1.链表阻塞队列–LinkedBlockingQueue原理

1.1.概述

1>.LinkedBlockingQueue是基于链表的阻塞队列,其内部维护了一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓冲在队列内部,而生产者立即返回.只有当队列缓冲区达到最大缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),会阻塞生产者线程,直到消费者从队列中消费掉一个数据,生产者线程才会被唤醒.反之,对于消费者这端的处理也基于同样的原理;

2>.LinkedBlockingQueue之所以能够高效的处理并发数据,是因为其对生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行的操作队列中的数据,以此来提高整个队列的并发性能;

3>.作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,那么LinkedBlockingQueue会默认初始化一个类似无限大小的容量(默认为Integer.MAX_VALUE),这样的话,如果生产者速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了;

4>.特性:

①.LinkedBlockingQueue支持有界;
②.LinkedBlockingQueue实现是链表;
③.LinkedBlockingQueue是懒惰的;
④.LinkedBlockingQueue每次入队会生成新Node;
⑤.LinkedBlockingQueue使用两把锁;

1.2.基本的入队出队

1.2.1.入队

public class LinkedBlockingQueue extends AbstractQueueimplements BlockingQueue, java.io.Serializable {static class Node {E item;/*** 下列三种情况之一* - 真正的后继节点* - 自己,发生在出队时* - null,表示是没有后继节点,是最后了*/Node next;Node(E x) { item = x; }}
}

1>.初始化链表 last = head = new Node(null); Dummy节点用来占位,item为null;
在这里插入图片描述
2>.当一个节点入队 last = last.next = node;
在这里插入图片描述
3>.再来一个节点入队 last = last.next = node;
在这里插入图片描述

1.2.2.出队

Node h = head;
Node first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;

1>.h = head;
在这里插入图片描述
2>.first = h.next;
在这里插入图片描述
3>.h.next = h;
在这里插入图片描述
4>.head = first;
在这里插入图片描述
5>.E x = first.item; first.item = null; return x;
在这里插入图片描述

1.3.加锁分析

1>.高明之处在于用了两把锁和dummy节点:

①.用一把锁,同一时刻,最多只允许有一个线程(生产者或消费者,二选一)执行;

②.用两把锁(锁住head和last),同一时刻,可以允许两个线程同时(一个生产者与一个消费者)执行;

  • 消费者与消费者线程仍然串行;
  • 生产者与生产者线程仍然串行;

2>.线程安全分析:

①.当节点总数大于2时(包括dummy节点),putLock保证的是last节点的线程安全,takeLock保证的是head节点的线程安全.两把锁保证了入队和出队没有竞争;

②.当节点总数等于2时(即一个dummy节点,一个正常节点)这时候,仍然是两把锁锁两个对象,不会竞争;

③.当节点总数等于1时(就一个dummy节点)这时take线程会被notEmpty条件阻塞,有竞争,会阻塞;

// 用户put(阻塞),offer(非阻塞)
private final ReentrantLock putLock = new ReentrantLock();
// 用户take(阻塞),poll(非阻塞)
private final ReentrantLock takeLock = new ReentrantLock();

1.4.put操作

public void put(E e) throws InterruptedException {if (e == null) throw new NullPointerException();int c = -1;Node node = new Node(e);final ReentrantLock putLock = this.putLock;// count 用来维护元素计数final AtomicInteger count = this.count;putLock.lockInterruptibly();try {// 满了等待while (count.get() == capacity) {// 倒过来读就好: 等待 notFullnotFull.await();}// 有空位, 入队且计数加一enqueue(node);c = count.getAndIncrement();// 除了自己 put 以外, 队列还有空位, 由自己叫醒其他 put 线程if (c + 1 < capacity)notFull.signal();} finally {putLock.unlock();}// 如果队列中有一个元素, 叫醒 take 线程if (c == 0)// 这里调用的是 notEmpty.signal() 而不是 notEmpty.signalAll() 是为了减少竞争signalNotEmpty();}

1.5.take操作

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)notEmpty.signal();} finally {takeLock.unlock();}// 如果队列中只有一个空位时, 叫醒 put 线程// 如果有多个线程进行出队, 第一个线程满足 c == capacity, 但后续线程 c < capacityif (c == capacity)// 这里调用的是 notFull.signal() 而不是 notFull.signalAll() 是为了减少竞争signalNotFull()return x;}

***注意:

由put线程唤醒put线程是为了避免信号不足!

相关内容

热门资讯

猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...