举个栗子:
包饺子的流程:
1.和面(一般都是一个人负责,没办法多线程完成)
2. 擀饺子皮
3. 包饺子 (第二步和第三步,这两步就可以多线程完成了)
现在有 A B C 三位大兄弟,共同完成上面包饺子的步骤,擀面杖,一般一个家庭中,只有一个擀面杖,以是会发生,三个线程都去竞争这个擀面杖,A 大兄弟,拿到擀面杖擀皮了,B C 就需要阻塞等待,以是,很明显,包饺子的方式适适用多线程的方式来实现,即A 是和面的,B C 负责擀皮和包饺子。每次都是 B C 的一位大兄弟擀一个皮,然后另一个大兄弟包一个饺子,再擀一个皮,再包一个饺子...
于是就可以分工协作:和面之后,三位大兄弟就要研究 擀皮 和 包饺子了:
上述模型中:A 和 B,A 和 C 之间的耦合性是比力强的!!!A 的代码中就需要计划到一些和 B 相干的操作,B 的代码中也涉及到一些和 A 的操作。同样的,A 的代码中也需要计划和 C 的操作,C 的代码也涉及到和 A 的操作。别的,假如 B 或者 C “挂了”,此时对于 A 的影响就很大,A 也可能就跟着 “挂” 了。 引入生产者消费者模型,就可以低落上述耦合度:
A 和 B,A 和 C 之间都不是直接交互了,而是通过队列在中心举行传话。此时,A 的代码中,只需要和队列交互就可以了,A 是并不知道 B 和 C 的存在的,同样的,B C 的代码,也只需要和队列举行交互,他们也是不知道 A 的存在的。
假如 B C “挂了”,对于 A 的影响是微乎其微的...假设后续假如要增长一个 D,A 的代码也是不用发生任何变革的。
引入生产者消费者模型,低落耦合度之后,也是需要付出一些代价的 ==》 需要加机器,即需要引入更多的硬件资源。
1. 上述形貌的阻塞队列,并非是简朴的数据结构,而是基于这个数据结构实现的服务器程序,又被摆设到单独的主机上了。我们称这种未“消息队列(message queue)”
2. 整个系统的结构更复杂了。即我们要维护的服务器更多了。
3. 效率问题。引入了中心商“阻塞队列”,是存在差价的。哀求从 A 发出来到 B 收到,这个过程中就需要履历队列的转发,这个过程中是存在一定开销的...
2. 削峰填谷
在讲代码之前,让我们先用一个栗子,来引入削峰填谷:
三峡水坝,大家应该都直到,是一个非常牛 x 的工程。
它的此中一项工作,就是可以使得上流的水流,按照固定的速率往下流去放水。
如下图所示:
上面是一个分布式系统的大抵模型,但我们要思量到的是,当外网的哀求突然增多时,即入口服务器 A 接收到的哀求数目增长许多,A 的压力就会变大,但因为 A 做的工作一般比力简朴,每个哀求消耗的资源是比力少的,但是 B 和 C 服务器就不一定了,他们的压力同样会很大,且假设:B 是用户服务器,需要从数据库中找到对应的用户信息,C 是商品服务器,也需要从数据库找到对应的商品,还需要一些规则举行匹配,过滤等等...
A 的抗压能力比力强,B C 的抗压能力比力弱(他们需要完成的工作可能更加复杂,每个哀求消耗的资源多...) ==》 一旦外界的哀求出现突发的峰值,就会直接到导致 B C 服务器挂了...
那为什么,当哀求多的时候,服务器就会挂了呢??? 服务器处理每个哀求,都是需要消耗硬件资源的!!!(包括但不限于 cpu 内存 硬盘 网络带宽等等...)即使一个哀求消耗的资源比力少,但也无法承受住,同时会有许多的哀求,加到一起来,如许消耗的总资源就多了。 ==》 上述任何一种硬件资源达到瓶颈,服务器都会挂(即客户端给服务器发出哀求,但服务器不会再举行相应返回了)....
外界客服端发起的哀求的数目,并不是固定的,有多少哀求,是属于‘客户的请问“。有多少的哀求,都是属于”客户的举动“...
我们就可以使用阻塞队列 / 消息队列了(阻塞队列:是以数据结构的视角命名的。消息队列:是基于阻塞队列实现服务器程序的视角命名的)...
当在 A 与 B C 之间添加一个阻塞队列之后,因为阻塞队列的特性,即使外界的哀求出现峰值,也是由队列来承担峰值的哀求,B 和 C(下游)仍然可以按照之前的速度来获得哀求,如许就可以有效的防止 B 和 C 被高峰值的冲击导致服务器”挂了“。
补充:当哀求太多的时候,接收哀求的服务器也会挂的。哀求一直往上增长,A 肯定也会有顶不住的时候,也可以给 A 前面再加一个阻塞队列,但当哀求进一步的增长,队列也是可能挂的...(引入更多的硬件资源,避免上述情况...)
阻塞队列对应的数据结构
差异线程之间的,put 和 take 方法中的 notify 可能会不正确的将错误的 wait 给唤醒。
或者出现如下情况:入队列的唤醒操作,把其他线程的入队列的 wait 唤醒了。在第一步中,两个 wait 都执行到 put 了(留意:wait 之后会有三步操作,第一步就是释放锁,以是可以出现两个 wait 都执行到 put 了),第二步,有一个 take 方法执行到了 notify,将此中一个 put 的 wait 唤醒了,然后这个 put 操作向下执行代码,执行到 notify 之后,将另一个 put 方法的 wait 唤醒了...
如上两种,又是不符合我们预期的两种 bug,并且似乎还很麻烦,锁的对象又必须是 locker 这一个对象,假如我们界说两个 locker1 和 locker2,那又无法实现锁竞争 ==》 线程不安全了...怎样办理呢?
其实办理方案很简朴,但是问题是为什么,一定要想明白。
在举行阻塞的时候,我们是都只是用了 if 来举行条件判断,在 put 方法中,使用 if(size >= elems.length) 判断,在 take 方法中,使用 if(size == 0) 来判断,if 是 “一锤子买卖”,只判定一次条件,一旦程序进入阻塞之后,再被唤醒,这中心隔的时间,就是沧海桑田了,阻塞状态的过程中,发生的时候,会导致出现许多变数。有了这些变数之后,就很难以保证,()中的条件是否仍然满足了,入队列的条件是否仍然具备了...
我们可以将 if 改为 while,改为 while 之后,意味着 wait 唤醒之后,还需要再判定一次条件。即,wiat 之前判定一次,唤醒之后,再判定一次(相称于多做了一步确认操作!!!) 假如再次判定条件,发现队列还是满的,即是在 wait 等待过程中,出现了变数,此时就应该继续等待!
Java 尺度库也是推荐,wait 要 搭配 while 举利用用,多 N 次确认操作!!!
上面英文的大概意思是: wait 可能被提前唤醒,即明明条件还没满足,就被唤醒了,以是经常是一个循环,以是我们可以使用 while 举行确认操作!