IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 多线程(五) -- 并发工具(二) -- J.U.C并发包(六) -- LinkedQueue详解 -> 正文阅读

[数据结构与算法]多线程(五) -- 并发工具(二) -- J.U.C并发包(六) -- LinkedQueue详解

LinkedBlockingDeque:

内部维护的节点对象:

public class LinkedBlockingDeque<E>
    extends AbstractQueue<E>
    implements BlockingDeque<E>, java.io.Serializable {
	 /** Doubly-linked list node class */
    static final class Node<E> {
        /**
         * The item, or null if this node has been removed.
         */
        E item;

		// JDK13中的,jdk8没有
        /**
         * One of:
         * - the real predecessor Node
         * - this Node, meaning the predecessor is tail
         * - null, meaning there is no predecessor
         */
        // Node<E> prev;

        /**
         * One of:
         * - the real successor Node
         * - this Node, meaning the successor is head
         * - null, meaning there is no successor
         */
        /**
         * 下列三种情况之
         * - 真正的后继节点
         * - 自己,发生在出列时
         * - null, 表示是没有后继节点,是最后了
        /
        Node<E> next;

        Node(E x) {
            item = x;
        }
    }
}

入队:

1.初始化链表:

last = head = new Node< E >(null);Dummy节点(哨兵节点)来占位(last和head都指向dummy节点),item为null
在这里插入图片描述

2.第一个节点入队:

last = last.next = node;
把新节点赋值为当前last节点的后节点,并把last节点设置为当前节点
在这里插入图片描述

3.第二个及之后的节点入队:

last = last.next = node;

在这里插入图片描述

出队:

Node<E> h = head;
Node<E> first = h.next;
h.next = h;
head = first;
E x = first.item;
first.item = null;
return x;
1. Node h = head

将临时变量h赋值给头节点
在这里插入图片描述

2. first = h.next;

将临时变量first赋值给h的下一个
在这里插入图片描述

3. h.next = h

将h的next指向自己,目的是不让next指向其他节点,保证此节点能安全的被垃圾回收

在这里插入图片描述

4. head = first

在这里插入图片描述

5.
E x = first.item;
first.item = null;
return x;

在这里插入图片描述

加锁分析:

高明之处在于加了2把锁:
  • 用一把锁,同一时刻,最多只允许有一个线程(生产者或消费者,二选一)执行
  • 用两把锁,同一时刻可以允许2个线程同事(一个生产者一个消费者)执行
    • 消费者与消费者线程仍然串行
    • 生产者与生产者线程仍然串行
线程安全分析:
  • 当节点总数大于2时(包括dummy节点),putLoc保证的是last节点的线程安全,takeLock保证的是head节点的线程安全。两把锁保证了入队和出队没有竞争
  • 当节点总数等于2时(即一个dummy节点,一个正常节点)这时候,仍然是两把锁锁两个对象,不会竞争
  • 当节点总数等于1时(就一个dummy节点),这时take线程会被noteEmpty条件阻塞,有竞争,会阻塞
// 用于put(阻塞), offer(非阻塞)
private final ReentrantLock putLock = new ReentrantLock();
// 用于take(阻塞),poll(非阻塞)
private final ReentrantLock takeLock = new ReentrantLock();
put操作
public void put(E e) throws InterruptedException {
    // 不能为空
    if (e == null) throw new NullPointerException();
    // Note: convention in all put/take/etc is to preset local var
    // holding count negative to indicate failure unless set.
    int c = -1;
    LinkedBlockingQueue.Node<E> node = new LinkedBlockingQueue.Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    // count维护元素计数
    final AtomicInteger count = this.count;
    // 可打断
    putLock.lockInterruptibly();
    try {
        /*
         * Note that count is used in wait guard even though it is
         * not protected by lock. This works because count can
         * only decrease at this point (all other puts are shut
         * out by lock), and we (or some other waiting put) are
         * signalled if it ever changes from capacity. Similarly
         * for all other uses of count in other wait guards.
         */
        // 队列容量满了,就等待
        while (count.get() == capacity) {
            // 倒过来读:等待不满
            notFull.await();
        }
        // 有空位,入队且计数加一
        enqueue(node);
        // 返回的是+1前的值
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            // 除了自己put以外,队列还有空位,由自己唤醒之前等待的put线程
            // 一次只唤醒一个,避免不必要的竞争
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    // 如果队列中中一个元素,叫醒take线程
    if (c == 0)
        // 一次只唤醒一个,避免不必要的竞争
        signalNotEmpty();
}
take操作

和put类型,不做分析

和Array性能比较:

主要列举LinkedBlockingQueue(推荐)和ArrayBlockingQueue的性能比较

  • Linked支持有界,Array强制有界
  • Linked实现是链表,Array是数组
  • Linked是懒惰的,而Array需要提前初始化Node数组
  • Linked每次入队会生成新Node,而Array的Node是提前创建好的
  • Linked两把锁,Array一把锁

ConcurrentLinkedQueue:

ConcurrentLinkedQueue的设计和LinkedBlockingQueue非常像,也是:

  • 两把锁,同一时刻,可以允许两个线程(一个生产者一个消费者)执行
  • dummy节点的引入让两把锁将来锁住的是不同对象,避免竞争
  • 只是这锁使用了cas来实现

事实上,ConcurrentLinkedQueue应用还是非常广泛的:
例如之前讲的Tomcat的Connector结构时,Acceptor作为生产者向Poller消费者传递事件信息时,正是采用了ConcurrentLinkedQueue将ScoketChannel给Poller使用:
在这里插入图片描述

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-02-28 15:50:37  更:2022-02-28 15:51:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 15:37:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码