先分享个有趣的故事给大家,是女大学生被男老师追求的事情,故事的结局不是你们想的那种老牛吃嫩草,吃不到就用强的那种,男的40,女的19,我个人感觉男的比较虚伪的认真了,这是我的个人看法,也可以说他爱的太深了,这个反正我是不太信,女的有点优柔寡断,明明不喜欢说出来就好,以怕挂科为理由,一直和老师瞎扯正义和大道理,现在的老师还不至于无缘无故就挂你科。故事到这了,不说了,反正恋爱的请珍惜,分手的祝你分手快乐祝你找个更好的,单身的对自己好点(爱护自己的双手)。
目录
- 常用阻塞场景
- 定义
- 核心方法
- java中阻塞队列种类
- 实现原理(源码查看)
- 使用场景
- 总结
1.常用的阻塞场景
- 队列中的生产者和消费者
生产者:是往队列里添加元素的线程
消费者:是从队列里拿元素的线程
阻塞队列就是容器,存放元素(别蒙蔽好吧,元素就是object) - 阻塞场景
1)当队列中没有数据的情况下,消费者端的所有线程被挂起(也就是阻塞了),直到有数据存放入队列
2)当队列中填满数据的情况下,生产者端的所有线程被挂起,直到队列中有空的位置,线程被自动唤醒
2.定义
符合1.2中两种场景的队列,称为阻塞队列
3.核心方法
放入数据的方法:
- offer(anObject):表示将anObject放入到队列BlockingQueue中,BlockingQueue中可以容纳就返回true,否则返回false。(本方法是不会阻塞当前执行方法的线程,反回false,有了解吗?)
- offer(E o,long timeout,TimeUnit unit):可以设定等待时间。如果在指定时间内还不能往队列中加入BlockingQuere,则返回false。
- put(anObjiect):和offer是一样的,但是失败它不会返回false,会直接阻塞当前方法的线程。
获取数据的方法:
- poll(long timeout,TimeUnit unit):从BlockingQueue中取出队首的对象。在指定时间内,队列一旦有数据可取,则立即返回队列中的数据,否则超过时间就返回失败。
- take():取走BlockingQueue里排在首位的对象。若BlockingQueue为空,则阻断进入等待状态,直到BlockingQueue有新的数据被加入。
- drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数)。通过该方法,可以提升获取数据的效率;无需多次分批加锁或者释放锁。
当然不单单就这6个方法,你可以点看一下,我就不一一介绍了。
4.java中阻塞队列种类
- ArrayBlockingQueue:由数组结构组成的有界阻塞队列
- LinkedBlockingQueue:由链表结构组成的有界组赛队列
- PriorityBlockingQueue:支持优先级排序的无界组赛队列
- DelayQueue:使用优先级排序的无界阻塞队列
- SynchronousQueue:不存储元素的组赛队列
- LinkedTransferQueue:由链表结构组成的无界组赛队列
- LinkedBlockingDeque:由链表结构组成的双向阻塞队列
特点:
1.通常情况下,用前两种就足够了。
2.PriorityBllockingQueue不能保证同优先级元素的顺序。
3.DelayQueue指定元素到期的时间,只有在元素到期时才能从队列中取走。
4.SynchronousQueue严格来说它并不是容器。由于队列没有容量,因此不能调用peek操作(返回队列头元素)
5.LinkedTransferQueue实现了一个重要接口TransferQueue该接口有5个方法,其中有3个比较重要的方法,一个是transfer(E e)如果没有消费者等待接受数据,会把元素插入到队列尾部,队列进入阻塞状态,直到有消费者取走该元素,tryTransfer(E e)看名字就知道不会阻塞线程,当然也可以给它传入时间,在指定的时间内元素未被消费者获取则返回false。
6.LinkedBlockingDeque是双向的队列,多线程同时入队时减少了一半的竞争。其中的First单词结尾的方法表示插入、获取或者移除双端队列的第一个元素;以Last单纯结尾的方法,表示插入、获取或者移除双端队列的最后一个元素。
5.实现原理(源码查看)
老规矩,打开我们的Source Insight 4.0,我以ArrayBlockingQueue为例,搜索看一下这个类定义了那些变量
我们维护的是一个Object的数组,takeIndex和putIndex分别表示队首元素和队尾元素的下标,count表示队列元素的个数,lock则是一个可重入锁,notEmpty和notFull是等待条件。在看看关键方法put
可以看出,先获取锁,在判断当前元素个数是否等于数组长度,如果相等,则调用notFull.await()进行等待。当此线程被其他线程唤醒时,通过enqueue(e)方法插入元素,最后解锁。在看看enqueue(e)方法
插入成功后,就通过notEmpty.signal()唤醒正在等待取元素的线程。再来看看take方法。
这个应该差不多懂了吧,和put方法类似,如果可以取,就通过dequeue方法取得元素,我们在看看dequeue方法得源码。
和enqueue方法类似,在获取元素后,通过等待条件notFull的signal方法唤醒正在等待插入元素的线程。
6.使用场景
第一种使用Object.wait()、Object.notify()和非阻塞队列实现生产者-消费者模式
package com.example.syt.myapplication;
import java.util.PriorityQueue;
/**
* Created by syt on 2019/4/15.
*/
public class Test {
private int queueSize=10;
private PriorityQueue<Integer> queue=new PriorityQueue<Integer>(queueSize);
public static void main(String[] args) {
Test test=new Test();
Producer producer=test.new Producer();
Consumer consumer=test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread{
@Override
public void run() {
while (true){
synchronized (queue){
while (queue.size()==0){
try{
System.out.println("队列空,等待数据");
queue.wait();
}catch (InterruptedException e){
e.printStackTrace();
queue.notify();
}
}
//每次移走队首元素
queue.poll();
queue.notify();
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
while (true){
synchronized (queue){
while (queue.size()==queueSize){
System.out.println("队列满,等待有余空间");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notify();
}
}
//每次插入一个元素
queue.offer(1);
queue.notify();
}
}
}
}
}
在看看我们使用阻塞队列去实现生产者-消费者模式
package com.example.syt.myapplication;
import java.util.concurrent.LinkedBlockingDeque;
/**
* Created by syt on 2019/4/15.
*/
public class Test {
private int queueSize=10;
private LinkedBlockingDeque<Integer> queue=new LinkedBlockingDeque<Integer>(queueSize);
public static void main(String[] args) {
Test test=new Test();
Producer producer=test.new Producer();
Consumer consumer=test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread{
@Override
public void run() {
while (true){
try {
queue.take();
System.out.println("取队首");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
try {
queue.put(1);
System.out.println("往队列加一个");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
总结
阻塞队列的特点:队空时消费阻塞,队满时生产阻塞。多线程消费数据起到了加速消费的作用,使得生产的数据不会在队里积压过多,而生产的数据也不会丢失处理了。