现在你已经知道了Java多线程编程的基础。但是,在实际编程过程中,你希望不要使用这种底层结构,通常,使用设计者设计好的上层结构比使用底层结构更加简单,而且安全。
很多线程问题可以使用一个或多个队列优雅而安全地解决。生产者线程向队列中插入元素,而消费者线程从队列中获取元素,队列可以让对象安全地从一个线程转移到另外一个线程。比如,考虑我们的银行转账程序。我们不直接操作对象,而是向队列中插入转账指令,另外一个线程读取转账指令,然后执行指令。只有这两个线程才有机会访问对象,我们也不需要同步。(当然,这个队列的设计者还是需要考虑线程问题的,但是你不需要)
阻塞队列会在队列满了但是你还在向里面写数据时,阻塞你的线程;在队列空了,但是你还在从里面读数据时,阻塞你的线程。阻塞队列是非常有用的工具,当两个线程分别进行读写操作时,他会自动调节两个线程的速度。它的方法主要包括:
方法 | 正常状态 | 特殊条件 |
add | 添加一个元素 | 如果队列满了,抛出IllegalStateException |
element | 返回第一个元素 | 如果队列空,抛出NoSuchElementException |
offer | 添加一个元素,返回true | 如果队列满了,返回false |
peek | 返回第一个元素 | 如果队列空,返回null |
poll | 返回并删除第一个元素 | 如果队列空,返回null |
put | 添加一个元素 | 如果队列满了,则阻塞 |
remove | 返回并删除第一个元素 | 如果队列为空,抛出NoSuchElementException |
take | 返回并删除第一个元素 | 如果队列为空,则阻塞 |
根据队列为空或满了时,队列的反应,可以将方法分为三类,如果你将它用于线程控制,你应该使用put和take方法。add, remove和element会抛出异常。但是,在多线程条件下,很多情况下,队列都是空的或者满的,所以你应该使用offer, poll和peek方法,他们会使用返回值表示队列状态。
需要注意,poll和peek方法使用null表示操作失败,所以队列中不允许存在null值。
offer和poll可以接受超时参数,比如
boolean success = q.offer(x,100,TimeUnit.MILLISECONDS);
会在100毫秒之内,尝试向队列中写入元素,如果成功,就返回true,否则返回false。类似
Object head = q.poll(100,TimeUnit.MILLISECONDS);
在100毫秒内尝试从队列中获取元素,如果成功,返回需要的值,否则,返回null表示失败。
java.util.concurrent包提供了其他阻塞队列的变体。默认情况下,LinkedBlockingQueue没有最大的大小限制。LinkedBlockingDeque是双端队列。ArrayBlockingQueue使用给定的大小构建队列,还有一个参数表示公平。当给定公平参数时,等待时间最长的线程会被特殊对待。通常,这种公平机制会使性能得到提升,你应该仅在需要他的时候使用。
PriorityBlockingQueue是优先队列,而不是先入先出队列。队列中的元素按照优先级顺序读取。队列长度不限,但是当队列为空时,线程会被阻塞。
最终,DelayQueue包含实现了Delayed接口的对象
interface Delayed extends Comparable<Delayed>
{
long getDelay(TimeUnit unit);
}
getDelay方法返回当前的延迟值,负值表示已经超时。只有当数据超时后,数据才能被移除。你还要实现compareTo方法,DelayQueue是用这个方法排序。