java中queue的使用

本文介绍阻塞队列的基本概念与工作原理,包括不同类型的阻塞队列及其特点。并通过一个具体示例展示了如何利用阻塞队列实现多线程间的协作,提高程序的效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Queue接口。Queue接口窄化 了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用。BlockingQueue 继承了Queue接口。



队列是一种数据结构.它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素就是说,队列以一种先进先出的方式管理数据,如果你试图 向一个已经满了的阻塞队列中添加一个元素或者是从一个空的阻塞队列中移除一个元索,将导致线程阻塞.在多线程进行合作时,阻塞队列是很有用的工具。工作者 线程可以定期地把中间结果存到阻塞队列中而其他工作者线线程把中间结果取出并在将来修改它们。队列会自动平衡负载。如果第一个线程集运行得比第二个慢,则 第二个线程集在等待结果时就会阻塞。如果第一个线程集运行得快,那么它将等待第二个线程集赶上来。下表显示了jdk1.5中的阻塞队列的操作:


add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null
put 添加一个元素 如果队列满,则阻塞
take 移除并返回队列头部的元素 如果队列为空,则阻塞



remove、element、offer 、poll、peek 其实是属于Queue接口。



阻塞队列的操作可以根据它们的响应方式分为以下三类:aad、removee和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。



最后,我们有阻塞操作put和take。put方法在队列满时阻塞,take方法在队列空时阻塞。



java.ulil.concurrent包提供了阻塞队列的4个变种。默认情况下,LinkedBlockingQueue的容量是没有上限的(说的不准确,在不指定时容量为Integer.MAX_VALUE,不要然的话在put时怎么会受阻呢),但是也可以选择指定其最大容量,它是基于链表的队列,此队列按 FIFO(先进先出)排序元素。


ArrayBlockingQueue在构造时需要指定容量,并可以选择是否需要公平性,如果公平参数 被设置true,等待时间最长的线程会优先得到处理(其实就是通过将ReentrantLock设置为true来达到这种公平性的:即等待时间最长的线程 会先操作)。通常,公平性会使你在性能上付出代价,只有在的确非常需要的时候再使用它。它是基于数组的阻塞循环队列,此队列按 FIFO(先进先出)原则对元素进行排序。


PriorityBlockingQueue是一个带优先级的队列,而不是先进先出队列。元素按优先级 顺序被移除,该队列也没有上限(看了一下源码,PriorityBlockingQueue是对PriorityQueue的再次包装,是基于堆数据结构 的,而PriorityQueue是没有容量限制的,与ArrayList一样,所以在优先阻塞队列上put时是不会受阻的。虽然此队列逻辑上是无界的, 但是由于资源被耗尽,所以试图执行添加操作可能会导致 OutOfMemoryError),但是如果队列为空,那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。另外,往入该队列中的元 素要具有比较能力。


最后,DelayQueue(基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。 下面是延迟接口:

Java代码
public interface Delayed extends Comparable<Delayed> {
long getDelay(TimeUnit unit);
}
放入DelayQueue的元素还将要实现compareTo方法,DelayQueue使用这个来为元素排序。



下面的实例展示了如何使用阻塞队列来控制线程集。程序在一个目录及它的所有子目录下搜索所有文件,打印出包含指定关键字的文件列表。从下面实例可以看出,使用阻塞队列两个显著的好处就是:多线程操作共同的队列时不需要额外的同步,另外就是队列会自动平衡负载,即那边(生产与消费两边)处理快了就会被阻塞掉,从而减少两边的处理速度差距。下面是具体实现:

Java代码
public class BlockingQueueTest {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("Enter base directory (e.g. /usr/local/jdk5.0/src): ");
String directory = in.nextLine();
System.out.print("Enter keyword (e.g. volatile): ");
String keyword = in.nextLine();

final int FILE_QUEUE_SIZE = 10;// 阻塞队列大小
final int SEARCH_THREADS = 100;// 关键字搜索线程个数

// 基于ArrayBlockingQueue的阻塞队列
BlockingQueue<File> queue = new ArrayBlockingQueue<File>(
FILE_QUEUE_SIZE);

//只启动一个线程来搜索目录
FileEnumerationTask enumerator = new FileEnumerationTask(queue,
new File(directory));
new Thread(enumerator).start();

//启动100个线程用来在文件中搜索指定的关键字
for (int i = 1; i <= SEARCH_THREADS; i++)
new Thread(new SearchTask(queue, keyword)).start();
}
}
class FileEnumerationTask implements Runnable {
//哑元文件对象,放在阻塞队列最后,用来标示文件已被遍历完
public static File DUMMY = new File("");

private BlockingQueue<File> queue;
private File startingDirectory;

public FileEnumerationTask(BlockingQueue<File> queue, File startingDirectory) {
this.queue = queue;
this.startingDirectory = startingDirectory;
}

public void run() {
try {
enumerate(startingDirectory);
queue.put(DUMMY);//执行到这里说明指定的目录下文件已被遍历完
} catch (InterruptedException e) {
}
}

// 将指定目录下的所有文件以File对象的形式放入阻塞队列中
public void enumerate(File directory) throws InterruptedException {
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory())
enumerate(file);
else
//将元素放入队尾,如果队列满,则阻塞
queue.put(file);
}
}
}
class SearchTask implements Runnable {
private BlockingQueue<File> queue;
private String keyword;

public SearchTask(BlockingQueue<File> queue, String keyword) {
this.queue = queue;
this.keyword = keyword;
}

public void run() {
try {
boolean done = false;
while (!done) {
//取出队首元素,如果队列为空,则阻塞
File file = queue.take();
if (file == FileEnumerationTask.DUMMY) {
//取出来后重新放入,好让其他线程读到它时也很快的结束
queue.put(file);
done = true;
} else
search(file);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
}
}
public void search(File file) throws IOException {
Scanner in = new Scanner(new FileInputStream(file));
int lineNumber = 0;
while (in.hasNextLine()) {
lineNumber++;
String line = in.nextLine();
if (line.contains(keyword))
System.out.printf("%s:%d:%s%n", file.getPath(), lineNumber,
line);
}
in.close();
}
}
### Java Queue接口及其实现类详解 #### 定义与基本特性 Java中的`Queue`接口表示一种通常遵循先进先出(FIFO, First In First Out)原则的数据结构,尽管如此,某些特殊类型的队列可能采用不同的插入、提取或遍历顺序[^2]。此接口属于Java集合框架的一部分,并扩展了`Collection`接口。 #### 方法概览 为了支持队列特有的行为模式——即元素入队(enqueue)和出队(dequeue),以及访问但不移除头部元素的操作,`Queue`接口提供了一系列方法。这些方法可以分为两个类别:抛出异常的方式和返回特定值的方式来指示成功与否;还有另外一对仅尝试执行操作的方法,在失败的情况下它们不会改变队列的状态并立即返回特定位值[^1]。 #### 实现类介绍 ##### LinkedList 作为一个实现了`List`和`Deque`(双端队列)接口的具体类,`LinkedList`同样兼容于`Queue`接口的要求。这意味着它可以被当作普通的双向列表使用的同时也能充当标准的队列角色。由于内部采用了双向链接节点存储数据项的设计方案,使得频繁地在两端添加或者删除元素变得高效。 ```java // 使用LinkedList作为Queue的例子 import java.util.LinkedList; import java.util.Queue; public class Example { public static void main(String[] args){ Queue<String> queue = new LinkedList<>(); queue.offer("First"); queue.offer("Second"); System.out.println(queue.poll()); // 输出 "First" System.out.println(queue.peek()); // 输出 "Second" } } ``` ##### PriorityQueue 不同于传统的FIFO队列,`PriorityQueue`按照自然排序或是由构造时指定的比较器所决定的顺序来排列其元素。当调用`poll()`方法取出下一个待处理的对象时,总是会得到当前具有最高优先级的那个成员。值得注意的是,默认情况下如果未给出显式的比较逻辑,则对象需实现`Comparable`接口以便能够相互之间进行大小关系判断[^4]。 ```java // 使用PriorityQueue的例子 import java.util.PriorityQueue; import java.util.Comparator; class Task implements Comparable<Task>{ private String name; @Override public int compareTo(Task other){ return this.name.compareTo(other.getName()); } // getter and setter... } public class PriorityExample{ public static void main(String[] args){ PriorityQueue<Task> pq = new PriorityQueue<>(new Comparator<Task>() { @Override public int compare(Task t1, Task t2) { return Integer.compare(t1.getImportance(),t2.getImportance()); } }); pq.add(new Task("Task A", 3)); pq.add(new Task("Task B", 1)); while (!pq.isEmpty()){ System.out.println(pq.poll().getName()); } } } ``` ##### LinkedBlockingQueue 对于并发编程环境而言,`LinkedBlockingQueue`是一个非常实用的选择。它是基于链表构建而成的一种阻塞型队列形式,具备良好的线程安全性特征,非常适合应用于生产者-消费者问题以及其他涉及多线程协作的工作流之中[^3]。 ```java // 使用LinkedBlockingQueue的例子 import java.util.concurrent.LinkedBlockingQueue; public class BlockingQueueExample { public static void main(String[] args)throws InterruptedException{ LinkedBlockingQueue<Integer> lbq = new LinkedBlockingQueue<>(10); lbq.put(1); // 可能会被阻塞直到有空间可用 System.out.println(lbq.take()); // 可能会被阻塞直到有元素可取 } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值