在多线程领域,所谓的阻塞,在某些情况下会挂起线程即阻塞,一旦满足某条件时,被挂起的线程又会自动被唤醒。队列的数据结构大家并不陌生,先进先出,先到先得,什么是阻塞队列呢,顾名思义,首先它是一个队列,当阻塞队列是空时,从队列中获取元素的操作将会被阻塞,当阻塞队列是满时,往队列里添加元素的操作将会被阻塞,那为什么需要阻塞队列呢,好处就是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,,在concurrent包发布之前,线程的控制,需要程序员自己来通过synchronized来控制线程的阻塞(wait)和唤醒(notify,notifyAll),当concurrent包发布之后这一切阻塞线程都为我们一手包办了,那阻塞队列有哪些种类呢。
以后的文章我们着重讲解ArrayBlockingQueue,LinkedBlockingQueue和SynchronousQuere,因为后边说到线程池的时候主要是这三个。
接下来,我们按照下边的表来一个一个介绍阻塞线程的方法类型。
方法类型 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
插入 | add | offer | put | offer |
移除 | remove | poll | take | poll |
检查 | element |
peek | 不可用 | 不可用 |
先来看,抛出异常的add,remove,和element的阻塞队列的例子
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class BlockingQueueDemo {
public static void main(String[] args) throws Exception {
BlockingQueue
blockingQueue = new ArrayBlockingQueue (3);
System.err.println(blockingQueue.add("a"));
System.err.println(blockingQueue.add("b"));
System.err.println(blockingQueue.add("c"));
// System.err.println(blockingQueue.add("d"));System.err.println(blockingQueue.element());
System.err.println(blockingQueue.remove());
System.err.println(blockingQueue.remove());
System.err.println(blockingQueue.remove());
// System.err.println(blockingQueue.remove());}
}
当add方法的注释放开的时候,会抛出下边的错误。
true
true
true
Exception in thread "main" java.lang.IllegalStateException: Queue full
at java.base/java.util.AbstractQueue.add(AbstractQueue.java:98)
at java.base/java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:326)
at BlockingQueueDemo.main(BlockingQueueDemo.java:12)
当remove方法的注释放开的时候,会抛出下边的错误,说明add方法,remove方法一言不和就抛出异常。
true
true
true
a
a
b
c
Exception in thread "main" java.util.NoSuchElementException
at java.base/java.util.AbstractQueue.remove(AbstractQueue.java:117)
at BlockingQueueDemo.main(BlockingQueueDemo.java:19)
再来看,不抛出异常的offer,peek,和poll的阻塞队列的例子
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class BlockingQueueDemo {
public static void main(String[] args) throws Exception {
BlockingQueue
blockingQueue = new ArrayBlockingQueue (3);
System.err.println(blockingQueue.offer("a"));
System.err.println(blockingQueue.offer("b"));
System.err.println(blockingQueue.offer("c"));
System.err.println(blockingQueue.offer("d"));System.err.println(blockingQueue.peek());
System.err.println(blockingQueue.poll());
System.err.println(blockingQueue.poll());
System.err.println(blockingQueue.poll());
System.err.println(blockingQueue.poll());}
}
从下边的执行结果来看,当往阻塞队列里追加元素的时候会返回false,以及当从空的阻塞队列中删除元素的时候,会返回null,这样程序相比直接抛出异常会显得友好些。
true
true
true
false
a
a
b
c
null
我们再来说说上表中第三种情况的put,take方法,阻塞,即不抛异常,也不返回结果,我们在看来看一个demo
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class BlockingQueueDemo {
public static void main(String[] args) throws Exception {
BlockingQueue
blockingQueue = new ArrayBlockingQueue (3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
System.err.println("===============");
//blockingQueue.put("d");blockingQueue.take();
blockingQueue.take();
blockingQueue.take();
//blockingQueue.take();
}}
首先创建容量为3的阻塞队列,当3个元素加满的时候,在想往容器中追加元素的时候,阻塞队列会一直等待,take方法也是一样的,当阻塞队列为空的时候,在想从队列当中take元素时,也是会阻塞。
最后,我们看看第四种情况,带时间参数的阻塞队列。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;public class BlockingQueueDemo {
public static void main(String[] args) throws Exception {
BlockingQueue
blockingQueue = new ArrayBlockingQueue (3);
System.err.println(blockingQueue.offer("a", 2, TimeUnit.SECONDS));
System.err.println(blockingQueue.offer("b", 2, TimeUnit.SECONDS));
System.err.println(blockingQueue.offer("c", 2, TimeUnit.SECONDS));
System.err.println(blockingQueue.offer("d", 2, TimeUnit.SECONDS));}
}
从执行结果来看,设定阻塞时间后,在指定的时间内没有添加成功的话,会返回结果,不阻塞,相比阻塞的方法也得友好。
true
true
true
false
阻塞队列以及阻塞队列的四种方法类型介绍到这里,实际的开发过程中,根据自己的业务场景来选择具体的方法类型,好的,下篇见。