Java集合--非阻塞队列(ConcurrentLinkedQueue基础)

1.0 非阻塞队列

在上篇中,我们讲到了阻塞队列,以及阻塞队列中的几个实现类。

本篇,我们继续对队列进行研究。而今天的主题,则是非阻塞队列!在非阻塞队列中,ConcurrentLinkedQueue是主要代表。

之前,我们了解了什么是阻塞队列,在此我们再简单地回顾下!

什么是阻塞队列?

阻塞,顾名思义:当我们的生产者向队列中生产数据时,若队列已满,那么生产线程会暂停下来,直到队列中有可以存放数据的地方,才会继续工作;而当我们的消费者向队列中获取数据时,若队列为空,则消费者线程会暂停下来,直到容器中有元素出现,才能进行获取操作。

这就是阻塞队列。

那么,非阻塞队列又是什么含义呢?

什么是非阻塞队列?

与阻塞队列相反,非阻塞队列的执行并不会被阻塞,无论是消费者的出队,还是生产者的入队。

在底层,非阻塞队列使用的是CAS(compare and set)来实现线程执行的非阻塞。

非阻塞队列的操作

与阻塞队列相同,非阻塞队列中的常用方法,也是出队和入队。

入队方法:

add():底层调用offer();

offer():Queue接口继承下来的方法,实现队列的入队操作,不会阻碍线程的执行,插入成功返回true;

出队方法:

poll():移动头结点指针,返回头结点元素,并将头结点元素出队;队列为空,则返回null;

peek():移动头结点指针,返回头结点元素,并不会将头结点元素出队;队列为空,则返回null;

下面,我们具体说下ConcurrentLinkedQueue的原理,以及实现!

ConcurrentLinkedQueue

ConcurrentLinkedQueue是一个线程安全的队列,基于链表结构实现,是一个无界队列,理论上来说队列的长度可以无限扩大。

与其他队列相同,ConcurrentLinkedQueue也采用的是先进先出(FIFO)入队规则,对元素进行排序。当我们向队列中添加元素时,新插入的元素会插入到队列的尾部;而当我们获取一个元素时,它会从队列的头部中取出。

因为ConcurrentLinkedQueue是链表结构,所以当入队时,插入的元素依次向后延伸,形成链表;而出队时,则从链表的第一个元素开始获取,依次递增;

不知道,我这样形容能否让你对链表的入队、出队产生一个大概的思路!

简单使用

值得注意的是,在使用ConcurrentLinkedQueue时,如果涉及到队列是否为空的判断,切记不可使用size()==0的做法,因为在size()方法中,是通过遍历整个链表来实现的,在队列元素很多的时候,size()方法十分消耗性能和时间,只是单纯的判断队列为空使用isEmpty()即可!!!

public class ConcurrentLinkedQueueTest {

    public static int threadCount = 100000;

    public static ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<String>();

    static class Offer implements Runnable {
        public void run() {
            if(queue.size()==0){
                String ele = new Random().nextInt(Integer.MAX_VALUE)+"";
                queue.offer(ele);
                System.out.println("入队元素为"+ele);
            }
        }
    }

    static class Poll implements Runnable {
        public void run() {
            if(!queue.isEmpty()){
                String ele = queue.poll();
                System.out.println("出队元素为"+ele);
            }
        }
    }

    public static void main(String[] agrs) {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for(int x=0;x<threadCount;x++){
            executorService.submit(new Offer());
            executorService.submit(new Poll());
        }
        executorService.shutdown();
    }
}

下篇中,我们详细来介绍ConcurrentLinkedQueue的底层实现。

引言:在笔者研究源码时,发现无论是idea,还是eclipse,在debug模式下,跟踪ConcurrentLinkedQueue源码时都会产生bug,具体情况就是debug控制台中类的内存地址和实际的内存地址不一致,导致代码在debug执行时并不会按照正常逻辑来执行。

详细描述,可参考如下内容:神奇的控制台

解决方案:将ConcurrentLinkedQueue源码拷出,本地新建一个类,使用run执行,在方法的前后增加自己的输出语句,打印出实际的内存地址,便可一探究竟。如果你不想对源码进行修改,只想用debug模式,建议将拷贝源码中的ConcurrentLinkedQueue的继承和实现统统去掉,形式如下:public class ConcurrentLinkedQueue<E>,这样也可以保证debug模式的正常运行。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏西二旗一哥

iOS - Dissecting objc_msgSend on ARM64

我们的朋友 ldp 又出现了。这回它读取了 x12 中指针,这个指针指向了要查找的bucket。每个bucket包含了一个选择器和一个 IMP 。 x9 现在包...

13240
来自专栏Android 研究

Java虚拟机基础——2JVM运行时数据区

本篇文章主要讲解JVM运行时数据区,所以我们按照线程是否私有的维度将本篇文章一分为二,分为线程私有数据区和所有线程共有的数据区。而在线程私有的数据区又可以分为程...

14750
来自专栏Web项目聚集地

你真的懂「类的加载机制」吗?

高广超 :多年一线互联网研发与架构设计经验,擅长设计与落地高可用、高性能互联网架构。目前就职于美团网,负责核心业务研发工作。本文首发在 高广超的简书博客,欢迎点...

19430
来自专栏陈树义

从字节码层面,解析 Java 布尔型的实现原理

最近在系统回顾学习 Java 虚拟机方面的知识,其中想到一个很有意思的问题:布尔型在虚拟机中到底是什么类型?

16620
来自专栏Python私房菜

简析Python中的四种队列

在Python文档中搜索队列(queue)会发现,Python标准库中包含了四种队列,分别是queue.Queue / asyncio.Queue / mult...

15730
来自专栏Java帮帮-微信公众号-技术文章全总结

第二十四天 多线程-多线程&线程安全【悟空教程】

进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。

14450
来自专栏编程心路

Java虚拟机内存管理(一)—内存划分

Java 虚拟机作为运行 Java 程序抽象出来的计算机,具有内存管理的能力,像内存分配、垃圾回收等这些相关的内存管理问题,Java 虚拟机都会帮我们解决,所以...

18350
来自专栏猿人谷

unix共享内存要点

共享内存优点:     1.在进程之间不通过内核传递数据,即不通过系统调用拷贝数据,达到快速,高效的数据传输。     2.随内核持续     *nix的共享内...

19750
来自专栏编程微刊

Node.js自学笔记之回调函数

26470
来自专栏java一日一条

深入理解Java运行时数据区

在本专栏的前12篇博客中, 我们主要大致介绍了什么是JVM, 并且详细介绍了class文件的格式。 对于深入理解Java, 或者深入理解运行于JVM上的其他语...

14310

扫码关注云+社区

领取腾讯云代金券