阻塞队列,关键在于阻塞,在阻塞队列中,线程阻塞有以下两种情况:
1.当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列;
2.当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒;
听起来有点不懂,那我们继续往下走:
1. ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
2. LinkedBlockingQueue :由链表结构组成的有界阻塞队列。
3. PriorityBlockingQueue :支持优先级排序的无界阻塞队列。
4. DelayQueue:使用优先级队列实现的无界阻塞队列。
5. SynchronousQueue:不存储元素的阻塞队列。
6. LinkedTransferQueue:由链表结构组成的无界阻塞队列。
7. LinkedBlockingDeque:由链表结构组成的双向阻塞队列
阻塞队列中几个主要方法的区别:
1. ArrayBlockingQueue
用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下 不保证访问者公平的访问队列,所谓公平访问队列是指阻塞的所有生产者线程或消费者线程,当 队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入 元素,先阻塞的消费者线程,可以先从队列里获取元素。通常情况下为了保证公平性会降低吞吐 量。我们可以使用以下代码创建一个公平的阻塞队列:
ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);
现在我们通过一个简单的生产者和消费者模式来熟悉有以下 ArrayBlockingQueue:
Product.java:
package cn.mxl.product.customer;
import java.util.concurrent.ArrayBlockingQueue;
public class Product implements Runnable{
private int i=0;
private ArrayBlockingQueue abq;
public Product( ArrayBlockingQueue abq) {
this.abq=abq;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(i<1000) {
System.out.println("添加的值:"+i);
abq.add(i++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Customer.java:
package cn.mxl.product.customer;
import java.util.concurrent.ArrayBlockingQueue;
public class Customer implements Runnable{
private ArrayBlockingQueue abq;
public Customer(ArrayBlockingQueue abq) {
this.abq=abq;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
try {
Integer take = abq.take();
System.out.println("取出的值:"+take);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Run.java:
package cn.mxl.product.customer;
import java.util.concurrent.ArrayBlockingQueue;
public class Run {
public static void main(String[] args) {
//生产者和消费和共用这一个容器,容量为10
ArrayBlockingQueue abq=new ArrayBlockingQueue(10);
Product pro=new Product(abq);
Thread t=new Thread(pro);
t.start();
Customer cus=new Customer(abq);
Thread tc1=new Thread(cus);
Thread tc2=new Thread(cus);
Thread tc3=new Thread(cus);
tc1.start();
tc2.start();
tc3.start();
}
}
运行结果:
这样一来,我们实现生产者与消费者模式就更加简单了.
为什么ArrayBlockingQueue实现生产和消费模式的时候是线程安全的呢?
查看它的add方法:
offer()方法说明由其子类实现:
ArrayBlockingQueue的取出方法take():
2. LinkedBlockingQueue
基于链表的阻塞队列,同 ArrayListBlockingQueue 类似,此队列按照先进先出(FIFO)的原则对 元素进行排序。而 LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其对于生产者 端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费 者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。 LinkedBlockingQueue 会默认一个类似无限大小的容量(Integer.MAX_VALUE)。
Product.java:
package cn.mxl.product.customer.Linked;
import java.util.concurrent.LinkedBlockingQueue;
public class Product implements Runnable{
private int i=0;
private LinkedBlockingQueue abq;
public Product( LinkedBlockingQueue abq) {
this.abq=abq;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(i<1000) {
System.out.println("添加的值:"+i);
// abq.add(i++);
try {
abq.put(i++); //阻塞式添加
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Customer.java:
package cn.mxl.product.customer.Linked;
import java.util.concurrent.LinkedBlockingQueue;
public class Customer implements Runnable{
private LinkedBlockingQueue abq;
public Customer(LinkedBlockingQueue abq) {
this.abq=abq;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
try {
Integer take = abq.take();
System.out.println("取出的值:"+take);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Run.java:
package cn.mxl.product.customer.Linked;
import java.util.concurrent.LinkedBlockingQueue;
public class Run {
public static void main(String[] args) {
//生产者和消费和共用这一个容器,容量为10
LinkedBlockingQueue abq=new LinkedBlockingQueue(10);
Product pro=new Product(abq);
Thread t=new Thread(pro);
t.start();
Customer cus=new Customer(abq);
Thread tc1=new Thread(cus);
Thread tc2=new Thread(cus);
Thread tc3=new Thread(cus);
tc1.start();
tc2.start();
tc3.start();
}
}
结果:
要了解它为什么是线程安全的,查看 ArrayListBlockingQueue.put()和take()方法:
ArrayListBlockingQueue和ArrayListBlockingQueue两个的区别在于第一个是两个独立锁,可以提高并发,ArrayListBlockingQueue可以实现公平与非公平锁:
ArrayListBlockingQueue的两个独立锁(它的构造函数中没有实现fairSync和nofairSync所以不能实现公平锁和非公平锁):
ArrayListBlockingQueue.的公平与非公平锁:
3. PriorityBlockingQueue
是一个支持优先级的无界队列。默认情况下元素采取自然顺序升序排列。可以自定义实现 compareTo()方法来指定元素进行排序规则,或者初始化 PriorityBlockingQueue 时,指定构造 参数 Comparator 来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。
User.java:
package cn.mxl.pro;
public class User implements Comparable{
private int i;
private Integer priority;
public Integer getPriority() {
return priority;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
/**
* @Description:当前对象和其他对象做比较,当前优先级大就返回-1,优先级小就返回1
* 值越小优先级越高
*/
@Override
public int compareTo(User user) {
return this.priority.compareTo(user.getPriority());
}
}
MyPriority.java:
package cn.mxl.pro;
import java.util.concurrent.PriorityBlockingQueue;
public class MyPriority {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
PriorityBlockingQueue pbq=new PriorityBlockingQueue();
int i=0;
while(i<10) {
User user=new User();
int priority=(int)(10*Math.random());
System.out.println(priority);
user.setI(i++);
user.setPriority(priority);
pbq.put(user);
}
i=0;
while(i<10) {
User take = pbq.take();
System.out.println("优先级权限:"+take.getPriority()+":"+take.getI());
}
}
}
运行结果:
关于它的线程安全问题,我就不去进行源码分析,方式差不多;
4. DelayQueue
是一个支持延时获取元素的无界阻塞队列。队列使用 PriorityQueue 来实现。队列中的元素必须实 现 Delayed 接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才 能从队列中提取元素。我们可以将 DelayQueue 运用在以下应用场景: 1. 缓存系统的设计:可以用 DelayQueue 保存缓存元素的有效期,使用一个线程循环查询 DelayQueue,一旦能从 DelayQueue 中获取元素时,表示缓存有效期到了。定时任务调度:使用 DelayQueue 保存当天将会执行的任务和执行时间,一旦从 DelayQueue 中获取到任务就开始执行,从比如 TimerQueue 就是使用 DelayQueue 实现的。
不做太多解释,工作中基本上不用;
5. SynchronousQueue
是一个不存储元素的阻塞队列。每一个 put 操作必须等待一个 take 操作,否则不能继续添加元素。 SynchronousQueue 可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线 程。队列本身并不存储任何元素,非常适合于传递性场景,比如在一个线程中使用的数据,传递给 另 外 一 个 线 程 使 用 , SynchronousQueue 的 吞 吐 量 高 于 LinkedBlockingQueue 和 ArrayBlockingQueue。
工作中基本上不用,不做过多举例;
6. LinkedTransferQueue
是 一 个 由 链 表 结 构 组 成 的 无 界 阻 塞 TransferQueue 队 列 。 相 对 于 其 他 阻 塞 队 列 , LinkedTransferQueue 多了 tryTransfer 和 transfer 方法。 1. transfer 方法:如果当前有消费者正在等待接收元素(消费者使用 take()方法或带时间限制的 poll()方法时),transfer 方法可以把生产者传入的元素立刻 transfer(传输)给消费者。如 果没有消费者在等待接收元素,transfer 方法会将元素存放在队列的 tail 节点,并等到该元素 被消费者消费了才返回。 2. tryTransfer 方法。则是用来试探下生产者传入的元素是否能直接传给消费者。如果没有消费 者等待接收元素,则返回 false。和 transfer 方法的区别是 tryTransfer 方法无论消费者是否 接收,方法立即返回。而 transfer 方法是必须等到消费者消费了才返回。 对于带有时间限制的 tryTransfer(E e, long timeout, TimeUnit unit)方法,则是试图把生产者传 入的元素直接传给消费者,但是如果没有消费者消费该元素则等待指定的时间再返回,如果超时 还没消费元素,则返回 false,如果在超时时间内消费了元素,则返回 true。
该类和LinkedBlokedQueue差不多,多了两个transfer方法.需要了解的可自行去查看;
7. LinkedBlockingDeque
是一个由链表结构组成的双向阻塞队列。所谓双向队列指的你可以从队列的两端插入和移出元素。 双端队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。相比其 他的阻塞队列,LinkedBlockingDeque 多了 addFirst,addLast,offerFirst,offerLast, peekFirst,peekLast 等方法,以 First 单词结尾的方法,表示插入,获取(peek)或移除双端队 列的第一个元素。以 Last 单词结尾的方法,表示插入,获取或移除双端队列的最后一个元素。另 外插入方法 add 等同于 addLast,移除方法 remove 等效于 removeFirst。但是 take 方法却等同 于 takeFirst,不知道是不是 Jdk 的 bug,使用时还是用带有 First 和 Last 后缀的方法更清楚。 在初始化 LinkedBlockingDeque 时可以设置容量防止其过渡膨胀。另外双向阻塞队列可以运用在 “工作窃取”模式中。
该类和LinkedBlokedQueue使用方式相似,不做过多举例.