对于许多线程问题,可以通过使用一个或多个队列以优雅且安全的方式将其形式化。生产者线程向队列插入元索,消费者线程则取出它们。使用队列,可以安全地从一个线程向另一个线程传递数据。例如,考虑银行转账程序,转账线程将转账指令对象插入一个队列中,而不是直接访问银行对象。另一个线程从队列中取出指令执行转账。只有该线程可以访问该银行对象的内部。因此不需要同步。(当然,线程安全的队列类的实现者不能不考虑锁和条件,但是,那是他们的问题而不是你的问题。)
当试图向队列添加元素而队列已满,或是想从队列移出元素而队列为空的时候,阻塞队列(blocking queue)导致线程阻塞。在协调多个线程之间的合作时,阻塞队列是一个有用的
工具。工作者线程可以周期性地将中间结果存储在阻塞队列中。其他的彐朴者线程移出中国结果并进一步加以修改。队列会自动地平衡负载。如果第一个线程集运行得比第二个慢,第二个线程集在等待结果时会阻塞。如果第一个线程集运行得快,它将等待第二个队列集赶上来。
阻塞队列示例如下:
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
public class BlockingQueueTest {
private static final int FILE_QUEUE_SIZE=10;
private static final int SEARCH_THERDS=100;
private static final File DUMMY=new File("");
private static BlockingQueue<File> queue=new ArrayBlockingQueue<>(FILE_QUEUE_SIZE);
public static void main(String[] args){
try(Scanner in=new Scanner(System.in)){
System.out.print("Enter base directory (e.g. /opt/jdk1.8.0/src): ");
String directory=in.nextLine();
System.out.print("Enter keyword (e.g. colatile): ");
String keyword=in.nextLine();
Runnable enumerator=()->{
try{
enumerate(new File(directory));
queue.put(DUMMY);
}catch (InterruptedException e){
}
};
new Thread(enumerator).start();
for(int i=1;i<=SEARCH_THERDS;i++){
Runnable searcher=()->{
try{
boolean done=false;
while(!done){
File file=queue.take();
if(file==DUMMY){
queue.put(file);
done=true;
}
else search(file,keyword);
}
}catch (IOException e){
e.printStackTrace();
}catch (InterruptedException e){
e.printStackTrace();
}
};
new Thread(searcher).start();
}
}
}
public static void enumerate(File directory) throws InterruptedException{
File[] files=directory.listFiles();
for(File file:files){
if(file.isDirectory()) enumerate(file);
else queue.put(file);
System.out.println(file.getName());
}
}
public static void search(File file,String keyword) throws IOException{
try(Scanner in=new Scanner(file,"UTF-8")){
int lineNumber=0;
while(in.hasNext()){
lineNumber++;
String line=in.nextLine();
if(line.contains(keyword))
System.out.printf("%s:%d:%s%n",file.getPath(),lineNumber,line);
}
}
}
}
生产者线程枚举在所有子目录下的所有文件并把它们放到一个阻塞队列中。这个操作很快,如果没有上限的话,很快就包含了所有找到的文件。我们同时启动了大量搜索线程。每个搜索线程从队列中取出一个文件,打开它,打印所有包含该关键字的行,然后取出下一个文件。我们使用一个小技巧在工作结束后终止这个应用程序。为了发出完成信号,枚举线程放置一个虚拟对象到队列中(这就像在行李输送带上放一个写着”最后一个包”的虚拟包)。当捜索线程取到这个虚拟对象时,将其放回并终止。
注意、不需要显式的线程同步。在这个应用程序中,我们使用队列数据结构作为一种同步机制。