可以考虑一个单向链表实现的队列(Queue)结构,在多线程环境下,多个线程同时对这个队列添加元素和取出元素的时候

势必要考虑采用锁的机制来进行同步以防止链表结构被破坏。

一般的做法是不管是读取还是写入的时候都使用同一把锁来进行互斥,这样实现起来比较简单但是却很低效。

本篇文章主要讲述的是对于一个链表实现的队列,通过采用头部锁和尾部锁的方式来分别对添加元素和取出元素进行互斥,

在多线程的环境中可以并行实现同时添加和取出操作(同时添加或者是同时取出必须要互斥)来达到队列数据高效读取的简单算法。

采用的语言是JAVA。

package twolocksample;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/* 这个类是用来放入单项链表队列中的元素类* */
class Node<E> {// 保存节点的数值private E value;// 单项链表的指向下一个节点的引用private Node<E> next;// 一个带值的构造函数public Node(E e) {value = e;}public void setValue(E value) {this.value = value;}public E getValue() {return value;}public void setNext(Node<E> next) {this.next = next;}public Node<E> getNext() {return next;}
}/* 这个类实现了一个简单的带两个同步锁单向队列,* 通过头部锁(只对headNode节点的读取进行互斥)和尾部锁* (只对tailNode的节点读取进行互斥)分别对offer和poll进行互斥* 从而可以让offer和poll同时并行进行,大幅度提高存储效率。* * 一般的两个同步锁队列里在队列为空的情况下加入offer第一个元素和* poll第一个元素的处理会发生头部锁尾部锁发生冲突而导致比较难处理的情况,* 而本实现通过在初始化队列里添加一个不起实际作用的哨兵节点来保证* 队列的任何时候都不会出现空的情况从而避免了,头部锁和尾部锁出现冲突的问题。* 也就是说这个队列里任何之后都至少有一个哨兵节点的存在。* */
public class TwoLockLinkQueue<E> {// 头部锁用来互斥多个poll的线程private Lock headLock = new ReentrantLock();// 尾部锁用来互斥多个offer的线程private Lock tailLock = new ReentrantLock();// 指向头部节点的引用private Node<E> headNode;// 指向尾部节点的引用private Node<E> tailNode;public TwoLockLinkQueue() {// 初始化时生成一个哨兵节点,让头和尾的引用都// 指向这个哨兵节点Node<E> sentinelNode = new Node<E>(null);headNode = sentinelNode;tailNode = sentinelNode;}// 放入一个元素到队列的最末端public boolean offer( E e ) {if(e == null) {return false;}// 【注意3】这里生成节点的时候直接将值赋到了节点里 Node<E> newNode = new Node<E>(e);tailLock.lock();try {// 【注意1】// 此处在队列为空(只有哨兵节点)的情况下与【注意2】// 的处理会发生冲突,冲突内容是此处要把哨兵节点的next设// 为新追加出来的节点,而【注意2】的处理要取出哨兵节点的next节点// 好在冲突的处理都是单次的内存读与写,实际上在CPU层面是原子// 操作,所以不会破坏链表的结构。// 最后,如果【注意1】的线程先执行【注意2】的线程后执行的话,【注意2】// 的poll处理会正常获取到【注意1】里offer进去的新节点,反之【注意2】// 会取到一个Null节点,这都是合情合理的。// 唯一一点要注意的是如果没有按照【注意3】的处理进行而是先把节点加入连表里,// 再给节点赋值的话,就会导致poll出来的节点里的值不正确的问题出现。tailNode.setNext(newNode);tailNode = newNode;} finally {tailLock.unlock();}return true;}// 从头部取出第一个元素public E pool() {headLock.lock();try {// 【注意2】// 请参考【注意1】的描述Node<E> newHeadNode = headNode.getNext();if( newHeadNode == null ) {return null;} else {// 这个算法巧妙在poll操作取出的实际不是第一个哨兵节点// 而且哨兵节点的Next节点的值,// 老的哨兵节点已经完成了它的任务被抛弃,现在将// 被poll出来的节点作为哨兵节点继续工作。headNode = newHeadNode;return newHeadNode.getValue();}}finally {headLock.unlock();}}// 简单起见我们用一个嵌套类来简单测试一下这个队列的正确性static class TestClass {// 用一个原子类来获取Offer到队列中的元素个数static AtomicInteger count = new AtomicInteger(0);// 主角登场static TwoLockLinkQueue<Integer> queue = new TwoLockLinkQueue<Integer>();public static void main(String[] args) {final int offerCount = 5;final int threadCount = 5;// 这个类是专门用来Offer数据的Runnable类class task1 implements Runnable {private TwoLockLinkQueue<Integer> queue;public task1(TwoLockLinkQueue<Integer> queue) {this.queue = queue;}public void run() {for(int i =0;i<offerCount;i++) {int value = count.addAndGet(1);queue.offer(value);System.out.println("["+Thread.currentThread()+"]"+"offer:"+value);}}}// 这个类是专门用来poll数据的Runnable类class task2 implements Runnable {private TwoLockLinkQueue<Integer> queue;public task2(TwoLockLinkQueue<Integer> queue) {this.queue = queue;}public void run() {while(!Thread.interrupted()) {Integer value = queue.pool();if(value != null) {System.out.println("["+Thread.currentThread()+"]"+"poll:"+value);}}}}// 使用一个线程池来管理所有线程ExecutorService pool = Executors.newCachedThreadPool();// 分别开启了offer的线程和poll的线程for(int i=0;i<threadCount;i++) {pool.execute(new task1(queue));pool.execute(new task2(queue));}pool.shutdown();// 等待一段时间之后结束所有的线程try{pool.awaitTermination(1000, TimeUnit.MILLISECONDS);} catch (InterruptedException e1){}pool.shutdownNow();  }}
}

参考的执行结果为

[Thread[pool-1-thread-1,5,main]]offer:1
[Thread[pool-1-thread-6,5,main]]poll:3
[Thread[pool-1-thread-4,5,main]]poll:2
[Thread[pool-1-thread-1,5,main]]offer:3
[Thread[pool-1-thread-4,5,main]]poll:4
[Thread[pool-1-thread-1,5,main]]offer:4
[Thread[pool-1-thread-1,5,main]]offer:5
[Thread[pool-1-thread-1,5,main]]offer:6
[Thread[pool-1-thread-4,5,main]]poll:5
[Thread[pool-1-thread-4,5,main]]poll:6
[Thread[pool-1-thread-5,5,main]]offer:7
[Thread[pool-1-thread-5,5,main]]offer:8
[Thread[pool-1-thread-4,5,main]]poll:7
[Thread[pool-1-thread-2,5,main]]poll:1
[Thread[pool-1-thread-5,5,main]]offer:9
[Thread[pool-1-thread-5,5,main]]offer:10
[Thread[pool-1-thread-6,5,main]]poll:8
[Thread[pool-1-thread-8,5,main]]poll:11
[Thread[pool-1-thread-5,5,main]]offer:11
[Thread[pool-1-thread-2,5,main]]poll:10
[Thread[pool-1-thread-9,5,main]]offer:12
[Thread[pool-1-thread-9,5,main]]offer:13
[Thread[pool-1-thread-9,5,main]]offer:14
[Thread[pool-1-thread-9,5,main]]offer:15
[Thread[pool-1-thread-4,5,main]]poll:9
[Thread[pool-1-thread-3,5,main]]offer:2
[Thread[pool-1-thread-9,5,main]]offer:17
[Thread[pool-1-thread-2,5,main]]poll:15
[Thread[pool-1-thread-6,5,main]]poll:14
[Thread[pool-1-thread-10,5,main]]poll:13
[Thread[pool-1-thread-8,5,main]]poll:12
[Thread[pool-1-thread-6,5,main]]poll:16
[Thread[pool-1-thread-2,5,main]]poll:18
[Thread[pool-1-thread-7,5,main]]offer:16
[Thread[pool-1-thread-4,5,main]]poll:17
[Thread[pool-1-thread-3,5,main]]offer:18
[Thread[pool-1-thread-4,5,main]]poll:19
[Thread[pool-1-thread-7,5,main]]offer:19
[Thread[pool-1-thread-3,5,main]]offer:20
[Thread[pool-1-thread-3,5,main]]offer:22
[Thread[pool-1-thread-3,5,main]]offer:23
[Thread[pool-1-thread-10,5,main]]poll:20
[Thread[pool-1-thread-4,5,main]]poll:21
[Thread[pool-1-thread-7,5,main]]offer:21
[Thread[pool-1-thread-7,5,main]]offer:24
[Thread[pool-1-thread-7,5,main]]offer:25
[Thread[pool-1-thread-2,5,main]]poll:23
[Thread[pool-1-thread-8,5,main]]poll:22
[Thread[pool-1-thread-6,5,main]]poll:25
[Thread[pool-1-thread-4,5,main]]poll:24

※最后再说几点

1.考虑到篇幅原因,测试代码中offerCount和threadCount的设值都比较小,大家可以进行调整以测试更多线程和更多次数读取时的动作

2.上面的执行结果看起来会有乱序的情况,那只是线程在打印结果的时候出现了乱序(因为在打印结果处理的地方没有做线程互斥)

实际队列里数据的添加和取出都是按照顺序进行的,链表也没有出现被破坏的情况

3.在JAVA中已经有了更加高效和功能强大的BlockingQueue的各种实现了,所以实际的编程中请使用Java的标准并行库

3.上面的代码也并不是没有现实意义,在一些底层C语言的嵌入式开发中可能没有像JAVA一样好用的并行库,所以可以参照上面的算法实现一个高效

线程安全的C语言的队列来,同样可以应用得很好。

[原创][Java]一个简单高效的线程安全队列的JAVA实现相关推荐

  1. 手撸一款简单高效的线程池(五)

    在之前的内容中,我们给大家介绍了 C++实现线程池过程中的一些常用线优化方案,并分析了不同机制使用时的利弊.这一篇,是线程池系列的最后一章.我们会介绍一下 CGraph 中的 threadpool 如 ...

  2. 四步快速配置一个简单高效的文本生成图像基准模型 T2I baseline

    本文将介绍一个简单高效的文本生成图像基准模型,该基准模型是DF-GAN20版代码,清楚简单,实用性高,本基准模型代码在他的基础上经过少量简化和处理,虚拟环境也进行了打包,非常适合作为一个基线模型,然后 ...

  3. java继承类型转换_#java 一个简单的例子理解java继承、成员函数重写、类型转换...

    一个简单的例子理解java继承.成员函数重写.类型转换 1.继承简介 举一个简单的例子:笔分为很多种,比如毛笔.钢笔.圆珠笔等等,这些笔都有一些相同的属性比如长度.笔迹的粗细等等:但他们也有不同的特点 ...

  4. java做一个简单的数据库,哪个嵌入式数据库用Java写成一个简单的键/值存储?

    我最近问了一个关于Neo4j的问题,我有工作,似乎很好.它是可嵌入的,它是用Java编写的,没有(太)许多依赖. 然而,它是一个图形数据库,我不知道这是一个好主意或不使用它作为一个简单的键/值存储. ...

  5. java点击按钮结线程_多线程的Java应用程序在调试工具Netbeans中单击“停止”按钮时输出一个奇怪的结果...

    我使用wait()和notify()机制学习了java中的多线程. 但我很好奇输出一个简单的多线程Java应用程序. 代码如下: class Q { int n; boolean valueSet = ...

  6. html如何绘制热图,推荐一个简单高效的图形化热图绘制工具

    热图(heatmap)绘制有很多方法,最多就是R了吧,Excel,MATLAB 也很常见,各种工具琳琅满目,这里我向大家推荐一款非常简单高效的热图绘制工具,更重要的是,它完全不需要命令行!!,完全满足 ...

  7. java多线程 --ConcurrentLinkedQueue 非阻塞 线程安全队列

    ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部:当我们获取一个元素时,它会返回队列头 ...

  8. java中什么是守护线程_什么是Java的守护线程?

    欢迎大家搜索"小猴子的技术笔记"关注我的公众号,有问题可以及时和我交流. 守护线程是一种支持性线程,主要用于后台调度以及支持性的工作.守护线程具备自动结束生命周期的特性,而非守护线 ...

  9. python 消息队列 go_gmq: gmq是基于redis提供的特性,使用go语言开发的一个简单易用的消息队列;支持延迟任务,异步任务,超时任务,优先级任务...

    1. 概述 gmq是基于redis提供的特性,使用go语言开发的一个简单易用的队列;关于redis使用特性可以参考之前本人写过一篇很简陋的文章Redis 实现队列; gmq的灵感和设计是基于有赞延迟队 ...

  10. 一个简单案例,5 分钟看懂 Java Lamdba 表达式

    JDK8引入了一个新玩意,叫做lamdba(那么大)的表达式,说得神乎其神,说真的,这玩意吧,并不难,但是要讲清楚吧,也不是太容易的事情. 从匿名内部类开始说起 老实交代,直接来讲lamdba表达式还 ...

最新文章

  1. linux iptables 详解
  2. 描述最常用的5种http方法的用途_对不起,来晚了,这可能是设计模式讲的最通俗易懂的文章(收藏)
  3. “注册编辑已被管理员停用”之解决办法
  4. php limit限流,php+redis 限流
  5. 添加元素的注意问题 复习 介绍 元素的创建 元素添加的方法 元素移除的方法
  6. Quartz2-Quartz与tomcat集成
  7. 绘制一幅蓝图_如何给新家绘制一张生活蓝图
  8. Fiddler2教程(Web调试工具)
  9. IRQL深入解析--IRQL级别
  10. 如何回复审稿人的意见?(总结)
  11. command命令大全(转自http://blog.dhedu.gov.cn/u/72/archives/2009/14290.html)
  12. linux完全卸载ffmpeg_RoboMaster视觉教程Linux(四)软件安装和卸载
  13. 免费音乐开放接口api_5种免费开放的音乐制作工具
  14. tomcat 严重: Could not contact localhost:80. Tomcat may not be running.
  15. 读《The C Programming Language》
  16. 数字人民币生态体系进一步完善 试点场景加速拓展
  17. 2020年蓝旭工作室暑期前端培训班Day1——HTML5 CSS3
  18. macbook上好用的解压软件_好用的Mac解压软件推荐
  19. 男人的责任--Cinderella Man
  20. 优思学院|做质量管理有七大工具,都是什么?

热门文章

  1. 油菜的做法及营养知识详细介绍
  2. 公鸡五钱,母鸡三钱,小鸡三只一文钱,求百钱买百鸡
  3. 本地网络出现了一个意外的情况,不能完成所有你在设置中所要求的更改
  4. android编辑视频,android 视频剪辑(亲测可用)
  5. C++获取SMART信息
  6. 银河帝国----基地与帝国
  7. iOS 【微信登录流程以及遇到的一些坑】
  8. ubuntu18.04 用LSD-SLAM跑通rgbd数据集以及地图重用
  9. 使用 Sun Jimi 进行图像格式转换
  10. 关于mysql的mysqlAccess denied for user'root'@'IP地址'