-
2021-08-22 12:31:30
目录
Java提供的线程安全的Queue可以分为阻塞队列和非阻塞队列,其中阻塞队列的典型例子是BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。
一、什么是阻塞队列
阻塞队列(BlockingQueue)首先是一个队列 ,也是一个支持两个附加操作的队列,这两个附加的操作支持阻塞的插入和移除方法。
当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。
当阻塞队列是满时,往队列里添加元素的操作将会被阻塞。
BlockingQueue提供的常用方法如下:
方法\处理方式 抛出异常 返回特殊值 一直阻塞 超时退出 插入方法 add(e) offer(e) put(e) offer(e,time,unit) 移除方法 remove() poll() take() poll(time,unit) 检查方法 add(e) peek() 不可用 不可用 在多线程领域,所谓堵塞,在某些情况下会挂起线程(即堵塞),一旦条件满足,被挂起的线程又会自动被唤醒。
二、阻塞队列种类分析
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界(但大小默认值为Integer.MAX_VALUE)阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的延迟无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列,也即单个元素的队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。简单介绍下其中三个队列:
1、ArrayListBlockingQueue
基于数组的阻塞队列,同LinkedBlockingQueue类似,内部维持着一个定长数据缓冲队列(该队列由数组构成)。ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。2、LinkedBlockingQueue
LinkedBlockingQueue默认大小是Integer.MAX_VALUE,可以设定大小,可以理解为一个缓存的有界等待队列。
基于链表的阻塞队列,内部维持着一个数据缓冲队列(该队列由链表构成)。当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。
LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。3、SynchronousQueue
SynchronousQueue是无界的,是一种无缓冲的等待队列,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加;可以认为SynchronousQueue是一个缓存值为1的阻塞队列,但是 isEmpty()方法永远返回是true,remainingCapacity() 方法永远返回是0,remove()和removeAll() 方法永远返回是false,iterator()方法永远返回空,peek()方法永远返回null。
声明一个SynchronousQueue有两种不同的方式,它们之间有着不太一样的行为。公平模式和非公平模式的区别:如果采用公平模式:SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体系整体的公平策略;但如果是非公平模式(SynchronousQueue默认):SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。三、非阻塞队列
基于锁的算法会带来一些活跃度失败的风险。如果线程在持有锁的时候因为阻塞I/O,页面错误,或其他原因发生延迟,很可能所有的线程都不能前进了。 一个线程的失败或挂起不应该影响其他线程的失败或挂起,这样的算法成为非阻塞(nonblocking)算法;如果算法的每一个步骤中都有一些线程能够继续执行,那么这样的算法称为锁自由(lock-free)算法。在线程间使用CAS进行协调,这样的算法如果能构建正确的话,它既是非阻塞的,又是锁自由的。非竞争的CAS总是能够成功,如果多个线程以一个CAS竞争,总会有一个胜出并前进。
非阻塞算法通过使用低层次的并发原语,比如比较交换,取代了锁。原子变量类向用户提供了这些底层级原语,也能够当做“更佳的volatile变量”使用,同时提供了整数类和对象引用的原子化更新操作。
-----摘自微信公众号 - Java编程指南(JavaXxzyfx),作者:程序员龙猫
基于链接节点的、无界的、线程安全。此队列按照 FIFO(先进先出)原则对元素进行排序。队列的头部 是队列中时间最长的元素。队列的尾部 是队列中时间最短的元素。新的元素插入到队列的尾部,队列检索操作从队列头部获得元素。当许多线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许 null 元素。
入队和出队操作均利用CAS(compare and set)更新,这样允许多个线程并发执行,并且不会因为加锁而阻塞线程,使得并发性能更好。
在并发编程中,一般推荐使用阻塞队列,这样实现可以尽量地避免程序出现意外的错误。阻塞队列使用最经典的场景就是socket客户端数据的读取和解析,读取数据的线程不断将数据放入队列,然后解析线程不断从队列取数据解析。还有其他类似的场景,只要符合生产者-消费者模型的都可以使用阻塞队列。
使用非阻塞队列,虽然能即时返回结果(消费结果),但必须自行编码解决返回为空的情况处理(以及消费重试等问题)。
另外他们都是线程安全的,不用考虑线程同步问题。
更多相关内容 -
完美解决JS文件页面加载时的阻塞问题
2020-11-30 00:19:351.XHR注入:就是用ajax异步请求同域包含脚本的文件,然后将返回的字符串转化为脚本使用,该方法不会造成页面渲染和onload事件的阻塞,因为是异步处理,推荐使用。 2.iframe注入:加载一个iframe框架,通过使用iframe... -
防止Node.js中错误导致进程阻塞的办法
2020-12-23 05:19:58防止错误导致的进程阻塞的方法主要有如下两种: 一. try-catch try-catch允许进行异常捕获,并让代码继续执行下去: 例如: 当函数抛出错误时,代码就停止执行了: (function() { var a = 0; a(); console.log(... -
交换机端口堵塞故障分析和解决方法
2020-10-01 14:06:48HIS的应用为大大提高医院的工作效率。随着医院业务的发展,HIS的规模越来越大,出现问题的环节也越来越多,本文介绍一例交换机故障的分析处理过程。 -
查找堵塞进程.sql
2019-07-05 15:53:30占用内存过高,查询死锁,等问题,可以执行,并Kill掉 -
Socket中InputStream的read方法的阻塞特性
2019-03-18 01:19:56NULL 博文链接:https://superherosk123.iteye.com/blog/611933 -
C++ 非阻塞式延时函数XSleep
2018-07-10 11:58:09// Function : XSleep() // Purpose : To make the application sleep for the specified time // duration. // Duration the entire time duration XSleep sleeps, it // keeps processing the message pump, to ... -
浅谈阻塞和非阻塞语句的本质区别
2020-10-22 14:19:52Verilog HDL是目前应用最为广泛的硬件描述语言。Verilog HDL可以用来进行各种层次的逻辑设计,也可以进行数字系统的逻辑综合,仿真验证和时序分析等。Verilog HDL适合算法级,寄存器级,逻辑级,门级和版图级等各个... -
android 学习笔记5-线程阻塞 UI更新
2017-01-02 18:48:591、查看网络上的图片 2、主线程阻塞-ANR 3、刷新UI-Handler 4、在本地缓存图片-例如微信的图片 5、获取开源代码 6、显示一个新闻客户端 7、使用GET方式提交表单数据 8、使用POST方式提交表单 -
阻塞队列——生产者消费者模型
2022-04-02 11:36:43目录 阻塞队列阻塞队列的功能生产者消费者模型生产者消费者模型的优点 阻塞队列 在学习数据结构时 我们了解到了什么是队列,普通队列遵守先进先出的规则,阻塞队列同样...若队列为满,尝试如队列也会出现堵塞,阻塞到队列🍉 阻塞队列
在学习数据结构时 我们了解到了什么是队列,普通队列遵守先进先出的规则,阻塞队列同样也符合这样的规则,但是相比于普通队列阻塞队列还有着其他的功能
🍉阻塞队列的功能
- 线程安全:阻塞队列是我们多线程中的案列之一,既然属于多线程,那么一定和线程安全有关系,阻塞队列主要的功能就是能保证线程安全
- 产生阻塞效果
1.若队列为空,尝试出队列,就会出现阻塞,阻塞到队列不为空为止
2 .若队列为满,尝试如队列也会出现堵塞,阻塞到队列不为满为止.
基于上述问题,我们就可以实现一个典型的生产者和消费者模型
🍉生产者消费者模型
我们举个场景🌰 :
过年的时候,大家家里都会包饺子,一家三口都会齐上阵,为了高效率的包,一般都会分工,假如👨擀饺子皮,👦和👩包饺子,这样效率会大大提高,👨擀完饺子皮之后会把饺子皮放在盖帘上,然后👦和👩将盖帘子上的饺子皮包成饺子,这就是一个典型的生产者消费者模型
生产者:👨
消费者:👦和👩
这里的盖帘子我们把他叫做交易场所盖帘子上饺子皮太多,会导致生产者阻塞,盖帘子没有更多的饺子皮,会导致消费者堵塞
生产者和消费者模型是实际开发中非常有用的一种多线程开发手段,尤其是在服务器开发中
假设有两个服务器A,B A作为入口服务器作为直接接受用户的网络请求,B作为应用服务器,来给A提供一些数据🍉生产者消费者模型的优点
- 优点一:能够让多个服务器之间充分的解耦合
何为解耦合?
打个比方:如果不使用生产者消费者模型,就像上面的图所示,这样的话A,B两个服务器耦合性是非常高的,在开发A是,需要充分的了解到B的接口是什么样的,开发B的时候也需要知道A是怎么调用的,一旦把B换成C,A是需要进行非常大的改动的,而且如果B挂了A也顺带着挂了,显然在计算机中这样的代码是非常不可取的,耦合性太高,在日常开发中我也需要尽量做到高内聚,低耦合
如果我们引进生产者消费者模型,就解决了这个问题
如图
当我们引进了阻塞队列后,就会产生两个生产者消费者模型
1. 对于请求:A是生产者,B是消费者
2.对于响应:A是消费者,B是生产者
阻塞队列就是A和B的交易场所A和B分别和阻塞队列进行交互,A不需要认识B,B也不需要认识A,队列是不会变的 B挂了,A也没啥影响,把B换成C,A也完全感知不到,这就达到了我们所说的低耦合的理念
- 优点二:能够对请求进行削峰填谷
如图,A作为入口服务器,计算量很轻,即使请求暴涨,A服务器也不会奔溃,但是B作为响应服务器,计算量很大,所需要的资源也很多,如果请求量暴涨,B就会挂掉
但是如果引进阻塞队列的话,就会有所改变
引进阻塞队列后就会起到削峰填谷的效果- 削峰
加入阻塞队列后,A将所有的请求放进了阻塞队列里,阻塞队列没有什么计算量,只是存储数据,所以几乎能容纳所有的A传过来的请求,B这边不会一下子增长很多请求,他会按照原来的速度处理数据,B就会得到很好的保护,不会因为请求的数据暴涨而崩溃,这就是削峰
- 填谷
像这种数据突然增长许多的情况只会维持很多的一段时间,等峰值过去后,数据增长又恢复了,B服务器不会因为请求的减少而闲下来,仍然会按照原有的速度通过阻塞队列来处理数据,这就叫做填谷
以上就是削峰填谷,其实这样的功能主要是为了保护像B这样的响应/应用服务器,保证程序不会因为数据的暴涨而崩溃
在现实生活中也有这样的🌰 :三峡大坝,不懂得小伙伴可以去了解一下
🥝阻塞队列的实现
🥝 Java中标准库的阻塞队列
我们先根据Java中内置的阻塞队列,基于他来实现一个生产者消费者模型,再来实现自己创建的阻塞队列
public static void main(String[] args) throws InterruptedException { BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(); blockingQueue.put("hello"); blockingQueue.take(); }
这就是一个简单的阻塞队列
🥝自己实现一个阻塞队列
- 先实现一个简单的队列
队列可以由数组和链表组成,数组实现起来呢更加简单,所以我们用数组实现,并且使用循环数组
1.设置三个变量分别的链表的头(head),尾(end),还有链表的长度(size);
2.入队列,把元素放在end的位置上,并且end++,size++;,如果end的值等于数组的长度,end设置为0;
3.出队列,head++,size–;如果head的值等于数组的长度head设置为0;
判断队列是否为空或者满,就需要判断size的值如果size=0,链表为空,在从队列取出元素时,产生堵塞,如果size==数组的长度,那么在放进元素时,也会产生堵塞下面是代码的实现
class MyBlockingQueue{ private int[] array = new int[1_0000]; private int head = 0; private int end = 0; private int size = 0; public void put(int value) throws InterruptedException { if(size == array.length){ return; } array[end] = value; end++; //需要处理end下表到达数组末尾的情况 if(end >= array.length){ end = 0; } size++; } public Integer take() throws InterruptedException { if(size == 0){ return null; } int ret = array[head]; head++; if(head>=array.length){ head=0; } size--; return ret; } }
- 保证阻塞队列的安全
从上面的代码我可以看出,take与put方法每一行代码都在对公共变量进行操作,所以我们直接对这两个方法进行加锁即可
class MyBlockingQueue{ private int[] array = new int[1_0000]; private int head = 0; private int end = 0; private int size = 0; synchronized public void put(int value) throws InterruptedException { if(size == array.length){ } array[end] = value; end++; //需要处理end下表到达数组末尾的情况 if(end >= array.length){ end = 0; } size++; } synchronized public Integer take() throws InterruptedException { if(size == 0){ } int ret = array[head]; head++; if(head>=array.length){ head=0; } size--; return ret; } }
- 实现堵塞效果
出队列,head++,size–;如果head的值等于数组的长度head设置为0;
判断队列是否为空或者满,就需要判断size的值如果size=0,链表为空,在从队列取出元素时,产生堵塞,如果size数组的长度,那么在放进元素时,也会产生堵塞关键使用wait和notify关键字
class MyBlockingQueue{ private int[] array = new int[1_0000]; private int head = 0; private int end = 0; private int size = 0; synchronized public void put(int value) throws InterruptedException { if(size == array.length){ this.wait(); } array[end] = value; end++; //需要处理end下表到达数组末尾的情况 if(end >= array.length){ end = 0; } size++; this.notify(); } synchronized public Integer take() throws InterruptedException { if(size == 0){ this.wait(); } int ret = array[head]; head++; if(head>=array.length){ head=0; } size--; this.notify(); return ret; } }
这样的一个简单的堵塞队列就完成了
‘下面创建一个实例来感受一下这样的阻塞队列🌰:`
public class TestDemo3 { private static MyBlockingQueue myBlockingQueue = new MyBlockingQueue(); public static void main1(String[] args) throws InterruptedException { BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(); blockingQueue.put("hello"); blockingQueue.take(); } public static void main(String[] args) { Thread producer = new Thread(()->{ int num = 0; while(true){ try { System.out.println("生产了"+num); myBlockingQueue.put(num); num++; //Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); producer.start(); Thread customer = new Thread(()->{ while(true){ try { int num = myBlockingQueue.take(); System.out.println("消费了"+num); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }); customer.start(); } }
运行结果
以上就是阻塞队列的相关基础知识
👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋
👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋
👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋 -
java阻塞队列实现原理及实例解析
2020-08-25 10:35:15主要介绍了java阻塞队列实现原理及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
异步 同步 阻塞 非阻塞 的说明_异步同步阻塞非阻塞_
2021-09-28 20:07:26异步、同步、阻塞、非阻塞的说明和解释,用于深入理解。 -
同步阻塞、同步非阻塞、异步阻塞、异步非阻塞之间的区别
2021-05-25 00:47:32同步阻塞、同步非阻塞、异步阻塞、异步非阻塞之间的区别先验知识此处的异步指的是什么同步、异步、阻塞、非阻塞同步阻塞、同步非阻塞、异步阻塞、异步非阻塞一个生动的例子总结与补充 先验知识 在解释这几个概念...同步阻塞、同步非阻塞、异步阻塞、异步非阻塞之间的区别
先验知识
在解释这几个概念之前,需要注意的是:
-
首先需要知道操作系统层面的同步、异步、阻塞这几个概念的含义。关于这方面的内容,可见笔者的另一篇博客:
同步与异步、并行与并发、阻塞与挂起:
https://blog.csdn.net/wangpaiblog/article/details/116114098 -
本文解释的概念至少适用于编程语言层面,但不适用于操作系统层面。原因是,在软件工程中,任何设计都可以进行分层封装。其中,每个中间层的设计对上层与下层来说都是透明的。因此,编程语言层面的“同步、异步、阻塞”与操作系统层面的没有必然联系。
-
有人喜欢对“同步阻塞、同步非阻塞、异步阻塞、异步非阻塞”的概念,首先区分发送方和接收方,但实际上,无论是发送还是接收,本质上都是一种“请求”的过程,如果不在操作系统层面进行分析,区分这两者实无必要。
-
同步、异步是针对两个任务(此任务不同于操作系统理论中的作业、进程等,指的只是一个线程中要做的一件事情。本文中的任务都指的是这个。)来说的,其中一个任务为一开始就在执行、所主要关注的,称为主任务,而另一个是与主任务相关的一个任务,称为相关任务。当主任务与相关任务分别位于不同的线程时,称该线程分别为主线程、相关线程。
-
阻塞是针对主任务所在的线程来说的。当主任务和相关任务位于同一个线程时,不存在“阻塞”,即此情况下不存在同步阻塞、异步阻塞。
但对于下面的情况,主任务和相关任务一定位于不同的线程:
-
主任务发起的是 I/O 请求
-
主任务发起的是网络请求
-
-
请求是一个期望获得资源的行为,而“同步阻塞、同步非阻塞、异步阻塞、异步非阻塞”描述的是获得期望资源之前的行为,而不是其之后的行为。这四个概念中的“阻塞”不描述在获得资源之后主线程的程序走向。
此处的异步指的是什么
首先需要明白,无论是对“异步”还是“非阻塞”的主线程,都不可能凭空接收到外界、执行没有事先设置的程序。而之所以主线程的行为会受到相关线程的影响,是因为主线程会周期性地调用一种“请求”函数,而此“请求”函数的行为会受相关线程的影响。
其次,对于操作系统,只要其启动,就一直在运行程序。如果没有显式的进程,就运行一个默认的空进程。另外,编程人员不可能事先预计用户本次对本软件的使用时长,然后在软件中设置一个运行时间与其正好相等的一种任务。这就是说,线程在执行时,实际上是在无限循环、周期性地执行一系列任务。于是,可以在每个任务之间插入一个中断点,用于执行额外的操作(任务),以实现对其它线程的交互,这就是异步操作。理由是,中断点之前的任务都已经完成,而任务之间本是不应有任务的(因为任务之间的部分属于空档,没有任何东西),因此,任务之间的中断点是由中断点之前的某任务插入的。由于某种原因,该任务并不想马上执行某操作,而是选择将其移至一系列任务执行完之后的某个任务之间的空档来执行,因此称为异步操作。
同步、异步、阻塞、非阻塞
前面已经指出了关键性的知识点,下面将直接给“同步阻塞、同步非阻塞、异步阻塞、异步非阻塞”的概念。为了更好的说明,笔者做了一张图,如下:
对于主线程上执行的一系列任务,如果其中的某个任务需要与相关线程交互,当其立即暂时当前的任务而发起请求,这称为同步;当该任务将请求的时间安排到某任务完成之后再发起,这称为异步。当发起请求时,直至获得完整的资源之后,不会继续执行现在或之后的任务,这称为阻塞;当发起请求时,立即获得瞬时的结果,然后继续执行现在或之后的任务,如果获得的瞬时资源不是完整的资源,将之后周期性发送类似的请求,直至获得完整的资源,这称为非阻塞。
可以看出,同步与异步的区别在于发起请求的时机,而阻塞与非阻塞的区别在于发起请求后是否对本线程进行暂停。
同步阻塞、同步非阻塞、异步阻塞、异步非阻塞
很多读者(包含笔者)都喜欢作者能直截了当地给出概念,而反感拐弯抹角和旁敲侧击。因此,笔者再提炼一下本文核心的四个概念:
-
同步阻塞:在需要某资源时马上发起请求,并暂停本线程之后的程序,直至获得所需的资源。
-
同步非阻塞:在需要某资源时马上发起请求,且可以马上得到答复,然后继续执行之后的程序。但如果得到的不是完整的资源,之后将周期性地的请求。
-
异步阻塞:在需要某资源时不马上发起请求,而安排一个以后的时间再发起请求。当到了那时发出请求时,将暂停本线程之后的程序,直至获得所需的资源。
-
异步非阻塞:在需要某资源时不马上发起请求,而安排一个以后的时间再发起请求。当到了那时发出请求时,可以马上得到答复,然后继续执行之后的程序。但如果得到的不是完整的资源,之后将周期性地的请求。
【提醒】
有些过分钻研概念的极客可能会对此提出质疑:既然“不马上发起请求”属于异步,但是如果一个线程将 CPU 分配的时间片用完了,此时应该得不到执行,那么同步非阻塞是不是可以归于异步非阻塞呢?
实际上不是这样。一方面,这里的“马上执行”,指的是针对这个程序中相关的代码而言的。基于上一条代码而执行的下一条代码就称为“被马上执行的代码”,而不管这两条代码的执行之间有没有被 CPU 中断。另一方面,评判
同步非阻塞
与异步阻塞
的其中一个标准是,如果一个主线程中,依次调用了 N 个同步非阻塞
的方法,则这些方法被执行的次序是不确定的。但如果一个主线程中,依次调用了 N 个异步阻塞
的方法,则当主线程完成当前的任务之后,转而真正执行这些异步方法时,这些方法被执行的次序与之前调用时一致。
一个生动的例子
一个贴切生动的例子可能对理解更有帮助。这里假设了这样的一种情景:笔者正在进行公司安排的一个“cleancode”专项需求(下面简称 cleancode 专项),然后突然对于门禁上报告的一项告警不太理解,笔者想要求助自己的同事(设该同事名为 Bob),于是在公司的通信软件(设该软件名为 contact)上向其发送了此求助消息,并假设笔者每天有减脂的诉求,因此在下午下班后不会立刻去吃饭。
前述的四个概念类比如下:
-
同步阻塞:笔者在 contact 上给 Bob 发了一条咨询信息,并开启 contact 的消息自动弹出功能。然后笔者暂停手头的工作,翘着二郎腿开始用手机摸鱼,直到手机上弹出 contact 的关于 Bob 的回复信息。
解释:
-
笔者、笔者的同事 Bob:两个位于不同服务器上的操作系统。
-
cleancode 专项:正在“笔者操作系统”上运行的一个线程。
-
笔者放弃工作上的任务:cleancode 专项线程被阻塞。
-
笔者开始摸鱼:cleancode 专项线程因阻塞而使笔者空闲,笔者通过调度来运行其它无关线程。
-
contact 的消息自动弹出:在操作系统中,用于唤醒阻塞线程的信号量。
-
-
同步非阻塞:笔者在 contact 上给 Bob 发了一条咨询信息,然后笔者继续做 cleancode 专项中的其它内容,并周期性查看 Bob 有没有回复。
-
异步阻塞:笔者在日程表上记录了这个待办事项,然后笔者继续做 cleancode 专项中的其它内容,最后到下午下班时,笔者在 contact 上给 Bob 发了一条咨询信息,并开启 contact 的消息自动弹出功能。然后笔者暂停手头的工作,翘着二郎腿开始在晚上加班时间用手机摸鱼,直到手机上弹出 contact 的关于 Bob 的回复信息。
-
异步非阻塞:笔者在日程表上记录了这个待办事项,然后笔者继续做 cleancode 专项中的其它内容,最后到下午下班时,笔者在 contact 上给 Bob 发了一条咨询信息,然后笔者继续加班做 cleancode 专项中的其它内容,并周期性查看 Bob 有没有回复。
总结与补充
-
用一句话概括本文的概念:
-
异步:把事情推到以后去做
-
阻塞:专心做一件事情
-
同步非阻塞:一边做一件事情,一边做另一件事情(一心二用)
-
异步阻塞:把问题推到以后专心处理
-
同步阻塞:马上专心做一件事情
-
异步非阻塞:把问题推到以后时不时处理一下
-
-
线程在被阻塞时,CPU 会将时间片分给其它线程。而当线程发出非阻塞线程请求时,它以后还要周期性地请求,这同样会占用 CPU 时间。因此,不能一味地认为异步非阻塞一定优于同步非阻塞。同样是异步非阻塞,底层实现不同,效率也不同。将程序中的所有代码都改成异步非阻塞,也未必可以提高系统的整体性能。哪种性能最好要取决于具体实现,而不是几个用于装蒜的术语。
-
Java 中没有异步关键字,所以一般情况下,Java 代码都是同步的,Java 中只有同步阻塞和同步非阻塞。但异步代码可以通过同步代码设计出来,所以 Java 中也可以设计出异步方法。
JavaScript 有异步关键字,但 JavaScript 是单线程的,所以 JavaScript 中只有同步阻塞和异步阻塞。
-
我们平常单独使用的“同步”一词,实际上指的是这里的同步阻塞,而“异步”指异步非阻塞。
-
在操作系统层面,只有单独的同步与异步、阻塞与非阻塞的说法。此时的同步与异步的含义主要有以下几个:
-
同本文的同步阻塞、异步非阻塞。
-
同步:强调两个程序的运行彼此有逻辑、时间上的先后关系。
异步:强调两个程序的运行彼此相对独立。
-
-
-
SQL Server中的SELECT会阻塞SELECT吗
2021-01-19 23:17:33而且从最常见的锁模式的兼容性表,我们可以看到IS锁与S锁都是兼容的,也就是说SELECT查询是不会阻塞SELECT查询的。 现有的授权模式 请求的模式 IS S U IX SIX X 意向共享 (IS) 是 是 ... -
运放的阻塞现象及其消除措施电路图
2020-08-03 09:38:41本文介绍了运放的阻塞现象及其消除的措施。 -
常见PHP堵塞案例
2021-04-11 13:21:21(下图为一个标准的同步堵塞IO)几个常见的场景:1.为什么一段非常简单的代码却把服务器搞崩溃了?2.为什么一个正常的不能再正常的Insert SQL却把MYSQL给卡死了?3.为什么我一个页面卡死了,其它新页面也无法打开?4....前言
众所周知,PHP是单线程同步堵塞模型,基于堵塞的模型,常常我们在开发中一个不小心就会把服务器搞崩,下面将跟大家一起分享常见的堵塞案例。(下图为一个标准的同步堵塞IO)
几个常见的场景:
1.为什么一段非常简单的代码却把服务器搞崩溃了?
2.为什么一个正常的不能再正常的Insert SQL却把MYSQL给卡死了?
3.为什么我一个页面卡死了,其它新页面也无法打开?
4.为什么消费者非常空闲,却无法消费?
常见的几个堵塞函数
TOP1:file_get_contents
最容易也最致命的函数TOP1
if(file_get_contents('http://www.xxx.com/api.php')){
//dosomething
}else{
//dosomething
}
file_get_contents不受set_time_limit的影响,也就是说,就算页面已经超时, 而file_get_contents进行依然会被挂起。
所以,如果一个远程地址卡死的话,所有调用file_get_contents的地方都会 全部挂起,从而引起服务器的502.
规避办法
从PHP5.0开始,file_get_contents可以支持context参数了, 所以如果一定要使用file_get_contents的话,一定要为其提供 context参数,设定timeout.
$param = array(
‘http’=>array(‘timeout’=>3,’method’=>’GET’)
);
echo file_get_contents('http://www.xxx.com/api.php',false, stream_context_create($param));
也可以使用curl替代file_get_contents.
$ch = curl_init('http://www.xxx.com/api.php');
curl_setopt_array($ch,
array(
CURLOPT_TIMEOUT=>3,
CURLOPT_CONNECTTIMEOUT=>5
)
);
echo curl_exec($ch);
TOP2:mysql_connect
数据库卡顿灾星TOP2
$conn = mysql_connect(‘ip:3306’, ‘root’, ‘root’);
if(!$conn){
die(‘mysql connect error’);
}
由于mysql_connect的timeout是受php.ini中的connect_timeout影响的,一般 都默认是30s。所以如果数据库连接不上,而在这30s中大量的进程都卡在连接 数据库上,那服务器出现502是必然的了。
规避办法
使用PDO的连接方式替代mysql_connect.PDO有着更加完善的属性配置,以及比mysql_connect更少一层的OO,所以稳定性上、扩展性上,效率上都比mysql_*更优秀。
$conn = new PDO($connect_string);
$conn->setAttribute(PDO::ATTR_TIMEOUT, 3);
//todo
或者使用fsockopen或者mysql_ping预访问数据库,防止进入堵塞死循环。
TOP3:session_start
文件锁引起的堵塞,体验非常差的函数TOP3
看一下例子,感受一下。
//1.php
@session_start();
sleep(10);
//2.php
@session_start();
echo ‘a’;
同一个用户,先访问1.php,再访问2.php,会发现只有等1.php中的sleep完成 以后,2.php才会显示出来a。这是因为session_start时,session文件会被锁死 只有被释放以后,其它程序才能继续。期间,其它程序一律会被堵塞。
规避办法
避免使用session,而且就算使用session,也要设置session_handler,避免文件死锁。
session.save_handler = memcache
session.save_path = "tcp://127.0.0.1:11211"
就算一定要使用session,也要做session_write_close。
//1.php
@session_start();
session_write_close();
sleep(10);
其它引起堵塞的场景
TOP1:忘记关闭连接
在非连接池的情况下,很多人都习惯打开链接以后就不管了,反正会有gc,但事实下,gc并没有值得信赖。
$conn = mysql_connect(…);
//dosomething
一般观念认为,页面在执行完成以后,PHP就会自动对该页面的所有变量进行 自动的变量回收。所以,在打开一个连接以后,根本不需要调用disconnect方法。
可是,mysql对连接资源的回收受my.cnf中的配置影响,在一定的时间内,如果 大量的连接没有被释放,则会引起严重的mysql拒绝连接的情况。
规避办法
1.养成打开连接和关闭连接的习惯;
2.使用持久连接,用链接池;
3.为类增加析构函数__destruct,自动关闭连接;
4.注册register_shutdown_function事件,自动释放变量和资源。
TOP2:大量并发UPDATE +1
很多人以为我只是普通的UPDATE啊,为什么会把数据库卡死?
$sql = ‘UPDATE `table` SET column=column+1 WHERE id=1’;
某统计模块,在对每一次请求的时候都会对某表的某字段进行自加1操作。 这个表非常简单,就两个字段,id和column,ID有索引且是主键。column是 int类型。 某开发者非常相信自己,说这个SQL绝对不会引起问题。
可结果是,某天,运维哥哥发现MYSQL大量的这个SQL处于wait状态。 上W条这个SQL一直阻塞着MYSQL,引起MYSQL的卡死。
原来后端某统计模块在对该表进行复杂的统计,造成了TABLE LOCK。从而阻塞 了所有的UPDATE。
规避办法
1.要相信程序,盲目的自信往往容易给自己挖坑;
2.相似操作的SQL,建议进行合并操作(insert many,increment, decrement);
3.多使用队列和多线程来提高批量任务的执行速度。redis和gearman 都是很不错的软件。
TOP3:consumer执行复杂运算
在队列中,单个consumer执行复杂的逻辑时,往往会卡住其它本机consumer。多个conumser之间尽量少涉及相同的数据行操作,避免锁引起的等待。
规避办法
1.consumer尽量执行比较轻的运算
2.复杂的运算转入worker,如gearman,充分利用分布式
3.加快队列的消费速度
-
设置路由器刷新堵塞网络的几种常见方法介绍
2020-10-01 22:30:20网络堵塞是造成网络变卡的原因,一般禁用本地连接再重新启用的这种方式最常用的,还有重启路由器等等,大家可以根据自己的情况自由选择,希望对大家有所帮助 -
阻塞线程池 阻塞线程池 阻塞线程池
2022-04-18 11:24:48阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池阻塞线程池... -
linux进程 阻塞和非阻塞操作
2021-05-10 03:47:24有时实现正确的 unix 语义要求一个操作不阻塞, 即便它不能完全地进行下去.有时还有调用进程通知你他不想阻塞, 不管它的 I/O 是否继续. 明确的非阻塞 I/O 由 filp->f_flags 中的 O_NONBLOCK 标志来指示. 这个标志... -
同步与异步、阻塞与非阻塞
2019-10-30 01:24:30对编程中四个概念的理解:同步(Synchronous)、异步(Asynchronous)、阻塞(Blocking)、非阻塞(Non-blocking)。 -
什么是阻塞和非阻塞?什么是同步和异步?什么是BIO、NIO、AIO?
2020-07-27 16:50:59一、什么是阻塞和非阻塞?什么是同步和异步? 1.1、阻塞与非阻塞 阻塞与非阻塞是描述进程在访问某个资源时,数据是否准备就绪的的一种处理方式。当数据没有准备就绪时: 阻塞:线程持续等待资源中数据准备完成,... -
阻塞IO和非阻塞IO(NIO)
2022-03-07 19:38:38首先我们来定义流的概念,不管是文件,...根据不同的处理办法,可以把IO分为阻塞或者非阻塞的: 阻塞:阻塞是个什么概念呢?比如某个时候你在等快递,但是你不知道快递什么时候过来,而且你没有别的事可以干(或者 -
SQLServer进程阻塞的检查和解决办法
2013-10-23 15:12:48SQLServer进程阻塞的检查和解决办法,有具体的代码,使用方便~ -
Java阻塞队列与非阻塞队列
2022-02-20 21:58:56什么是阻塞与非阻塞 阻塞和非阻塞指的是调用者在等待返回结果时的状态。阻塞时,在调用结果返回前,当前线程会被挂起,并在得到结果之后返回。非阻塞时,如果不能立刻得到结果,则该调用者不会阻塞当前线程。因此... -
tcp 阻塞与非阻塞
2021-08-05 17:26:40在阻塞模式下, send函数的过程是将应用程序请求发送的数据拷贝到发送缓存中发送就返回.但由于发送缓存的存在,表现为:如果发送缓存大小比请求发送的大小要大,那么send函数立即返回,同时向网络中发送数据;否则,send会... -
mysql几种常见的阻塞问题
2020-12-27 17:56:40文章目录1.表级锁导致的阻塞 1.表级锁导致的阻塞 -
socket阻塞和非阻塞
2021-03-24 15:59:17socket阻塞和非阻塞有哪些不同 1. 建立连接 阻塞方式下,connect首先发送SYN请求到服务器,当客户端收到服务器返回的SYN的确认时,则connect返回,否则的话一直阻塞。 非阻塞方式,connect将启用TCP协议的三次握手,... -
阻塞队列(BlockingQueue)
2021-01-13 16:27:29二、非阻塞队列(AbstractQueue) 1.优先级队列(priority queue) 2.ConcurrentLinkedQueue 三、阻塞队列(BlockingQueue) 1.ArrayBlockingQueue 2.LinkedBlockingQueue 一、队列(Queue) Queue是数据结构... -
基于VS2010的 TCP协议连接的例子,包括阻塞和非阻塞方式
2019-03-25 14:05:43基于VS2010 使用TCP协议进行网口连接的例子,包括阻塞和非阻塞方式,以及超时设置,使用非阻塞方式进行连接,使用阻塞方式进行收发