什么是阻塞队列
阻塞队列也是Queue队列的一种,叫BlockingQueue,所以BlockingQueue继承了 Queue。另外Queue 和 BlockingQueue 都是在 Java 5 中加入的。
public interface BlockingQueue<E> extends Queue<E> {
BlockingQueue是线程安全的,所以很多时候我们可以利用这个特性,去解决业务中的问题,比如在使用生产者/消费者模式的时候,生产者只需要往队列里添加元素,消费者只需要从队列里取出它们就可以了。
阻塞队列关键的两个方法在于put 和 take,put的作用是插入元素,但是当队列满了的时候,不会抛出异常,也不会返回false,而是让插入的线程处于等待状态,直到队列有空闲空间,当其他线程调用take的时候,此时队列就会释放之前处于阻塞的线程,并把刚才那个元素添加进去。
所以take 方法的作用是获取并移除队列的头结点,但是如果队列中没有数据时,他会阻塞,直到队列中有数据。一旦有了,就会立刻解除阻塞状态,并且取到数据。
常见的阻塞队列
ArrayBlockingQueue
ArrayBlockingQueue 是最典型的有界队列,内部是用数组存储元素的,利用 ReentrantLock实现线程安全。
我们创建他时,需要指明容量,拿第二个构造方法来说,第一个参数是容量,第二个参数是是否公平,如果设置为公平的话,那么等待时间最长的线程会优先被处理,如果设置为非公平的,那么就存在插队的可能,但是公平策略同时会带来一定的性能损耗。
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
示例:
public class BlockingDemo {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> arrayBlockingQueue =new ArrayBlockingQueue(3);
new Thread(() -> {
for (int i = 0; i < 6; i++) {
try {
arrayBlockingQueue.put(i+"");
System.out.println("put");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(1000);
new Thread(()->{
while (true){
String take = null;
try {
take = arrayBlockingQueue.take();
System.out.println("取出:"+take);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
put
put
put
取出:0
put
取出:1
put
取出:2
put
取出:3
取出:4
取出:5
LinkedBlockingQueue
LinkedBlockingQueue内部用链表实现的。如果创建时不指定它的初始容量,那么它容量默认就为整型的最大值 Integer.MAX_VALUE,由于这个数非常大,我们通常不可能放入这么多的数据,所以 LinkedBlockingQueue 也被称作无界队列,代表它几乎没有界限。
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
示例:
public class BlockingDemo {
public static void main(String[] args) throws InterruptedException {
LinkedBlockingQueue<String> blockingQueue =new LinkedBlockingQueue();
new Thread(() -> {
for (int i = 0; i < 6; i++) {
try {
blockingQueue.put(i+"");
System.out.println("put");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(1000);
new Thread(()->{
while (true){
String take = null;
try {
take = blockingQueue.take();
System.out.println("取出:"+take);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
put
put
put
put
put
put
取出:0
取出:1
取出:2
取出:3
取出:4
取出:5
SynchronousQueue
SynchronousQueue 最大的不同之处在于,它的容量为 0,所以没有一个地方来暂存元素,导致每次取数据都要先阻塞,直到有数据被放入,相反,每次放数据的时候也会阻塞,直到有消费者来取。
public SynchronousQueue() {
this(false);
}
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
示例交替打印
public class BlockingDemo {
public static void main(String[] args) throws InterruptedException {
String[] str1={"a","b","c","d","e","f"};
String[] str2={"1","2","3","4","5","6"};
SynchronousQueue<String> blockingQueue =new SynchronousQueue();
new Thread(()->{
for (int i = 0; i < str1.length; i++) {
try {
blockingQueue.put(str1[i]);
System.out.print(blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
for (int i = 0; i < str1.length; i++) {
try {
System.out.print(blockingQueue.take());
blockingQueue.put(str2[i]);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
输出:a1b2c3d4e5f6
PriorityBlockingQueue
PriorityBlockingQueue 是一个支持优先级的无界阻塞队列,可以通过自定义类实现 compareTo() 方法来指定元素排序规则,或者初始化时通过构造器参数 Comparator 来指定排序规则。同时,插入队列的对象必须是可比较大小的,也就是 Comparable 的,否则会抛出 ClassCastException 异常。
public class BlockingDemo {
public static void main(String[] args) throws InterruptedException {
PriorityBlockingQueue blockingQueue =new PriorityBlockingQueue();
blockingQueue.put(new Dog(1));
blockingQueue.put(new Dog(10));
blockingQueue.put(new Dog(8));
blockingQueue.put(new Dog(2));
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
}
static class Dog implements Comparable<Dog>{
private int age;
public Dog(int age) {
this.age = age;
}
@Override
public String toString() {
return age+"";
}
@Override
public int compareTo(Dog o) {
return o.age-age;
}
}
}
10
8
2
1
DelayQueue
DelayQueue是一个具有“延迟”功能的队列,这种队列是有序的,越靠近队列头代表越早过期。DelayQueue用于放置实现了Delayed接口的对象,Delayed 接口继承自 Comparable,里面的getDelay需要我们实现,getDelay 方法返回的是“还剩下多长的延迟时间才会被执行”,如果返回 0 或者负数则代表任务已过期,才能从队列中取走。
DelayQueue中内部使用的是PriorityQueue存放数据,使用ReentrantLock实现线程同步。
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> {
private final transient ReentrantLock lock = new ReentrantLock();
private final PriorityQueue<E> q = new PriorityQueue<E>();
}
示例:
public class MyData implements Delayed {
private long expire;
private int id;
private long start;
public MyData(int seconds) {
this.expire = System.currentTimeMillis()+TimeUnit.SECONDS.toMillis(seconds);
this.start=System.currentTimeMillis();
this.id = seconds;
}
@Override
public long getDelay(TimeUnit unit) {
long convert = unit.convert(expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
return convert;
}
@Override
public int compareTo(Delayed o) {
return (int)(this.getDelay(TimeUnit.MILLISECONDS)-o.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public String toString() {
return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()-this.start) +"秒";
}
}
public static void main(String[] args) throws IOException, InterruptedException {
DelayQueue<MyData> queue =new DelayQueue<>();
for (int i = 1; i <=5 ; i++) {
queue.offer(new MyData(i));
}
for (;;){
System.out.println(queue.take());
}
}
1秒
2秒
3秒
4秒
5秒