因此,我有一个大文本文件,在这种情况下约为4.5 GB,因此我需要尽快处理整个文件。现在,我使用3个线程(不包括主线程)对此线程进行了多线程处理。输入线程用于读取输入文件,处理线程用于处理数据,输出线程用于将处理后的数据输出到文件。
当前,瓶颈是处理部分。因此,我想在混合中添加更多处理线程。但是,这造成了一种情况,我有多个线程访问同一个BlockingQueue,因此它们的结果不能保持输入文件的顺序。
我正在寻找的功能示例如下: 输入文件:1、2、3、4、5 输出文件:^相同。不是2、1、4、3、5或任何其他组合。
我编写了一个虚拟程序,该虚拟程序的功能与实际程序相同,但要减去处理部分(由于处理类包含机密信息,因此我无法提供实际程序)。我还应该提到,所有类(输入,处理和输出)都是包含在Main类中的所有内部类,Main类包含initialize()方法和下面列出的主线程代码中提到的类级别变量。
主线程:
static volatile boolean readerFinished = false; // class level variables
static volatile boolean writerFinished = false;
private void initialise() throws IOException {
BlockingQueue<String> inputQueue = new LinkedBlockingQueue<>(1_000_000);
BlockingQueue<String> outputQueue = new LinkedBlockingQueue<>(1_000_000); // capacity 1 million.
String inputFileName = "test.txt";
String outputFileName = "outputTest.txt";
BufferedReader reader = new BufferedReader(new FileReader(inputFileName));
BufferedWriter writer = new BufferedWriter(new FileWriter(outputFileName));
Thread T1 = new Thread(new Input(reader, inputQueue));
Thread T2 = new Thread(new Processing(inputQueue, outputQueue));
Thread T3 = new Thread(new Output(writer, outputQueue));
T1.start();
T2.start();
T3.start();
while (!writerFinished) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
reader.close();
writer.close();
System.out.println("Exited.");
}
输入线程:(请原谅注释的调试代码,使用它来确保读取器线程实际上正确执行了。)
class Input implements Runnable {
BufferedReader reader;
BlockingQueue<String> inputQueue;
Input(BufferedReader reader, BlockingQueue<String> inputQueue) {
this.reader = reader;
this.inputQueue = inputQueue;
}
@Override
public void run() {
String poisonPill = "ChH92PU2KYkZUBR";
String line;
//int linesRead = 0;
try {
while ((line = reader.readLine()) != null) {
inputQueue.put(line);
//linesRead++;
/*
if (linesRead == 500_000) {
//batchesRead += 1;
//System.out.println("Batch read");
linesRead = 0;
}
*/
}
inputQueue.put(poisonPill);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
readerFinished = true;
}
}
处理线程:(通常这实际上会对行做一些事情,但是出于模型的目的,我只是将其立即推送到输出线程)。如有必要,我们可以通过使线程在每行中休眠一小段时间来模拟它的工作量。
class Processing implements Runnable {
BlockingQueue<String> inputQueue;
BlockingQueue<String> outputQueue;
Processing(BlockingQueue<String> inputQueue, BlockingQueue<String> outputQueue) {
this.inputQueue = inputQueue;
this.outputQueue = outputQueue;
}
@Override
public void run() {
while (true) {
try {
if (inputQueue.isEmpty() && readerFinished) {
break;
}
String line = inputQueue.take();
outputQueue.put(line);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出线程:
class Output implements Runnable {
BufferedWriter writer;
BlockingQueue<String> outputQueue;
Output(BufferedWriter writer, BlockingQueue<String> outputQueue) {
this.writer = writer;
this.outputQueue = outputQueue;
}
@Override
public void run() {
String line;
ArrayList<String> outputList = new ArrayList<>();
while (true) {
try {
line = outputQueue.take();
if (line.equals("ChH92PU2KYkZUBR")) {
for (String outputLine : outputList) {
writer.write(outputLine);
}
System.out.println("Writer finished - executing termination");
writerFinished = true;
break;
}
line += "\n";
outputList.add(line);
if (outputList.size() == 500_000) {
for (String outputLine : outputList) {
writer.write(outputLine);
}
System.out.println("Writer wrote batch");
outputList = new ArrayList<>();
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
}
现在,一般数据流非常线性,看起来像这样:
输入>处理>输出。
但是我想拥有的是这样的东西:
但是要注意的是,当数据输出时,需要将其排序为正确的顺序,或者必须已经按照正确的顺序进行排序。
有关此操作的建议或示例将不胜感激。
过去,我曾使用Future和Callable接口来解决诸如此类的并行数据流的任务,但不幸的是,代码不是从单个队列中读取的,因此这里的帮助很小。
对于那些会注意到这一点的人,我还应该补充一点,batchSize和poisonPill通常是在主线程中定义的,然后通过变量传递,它们通常不是 硬编码的在输入线程的代码中,输出检查编写器线程。在凌晨1点写实验模型时,我只是有点懒。
编辑:我还应该提到,这最多是使用Java 8所必需的。由于要在运行该程序的环境中未安装这些版本,因此无法使用Java 9及更高版本的功能。
答案 0 :(得分:2)
您可以做什么:
另外,由于每个线程都有一个输入队列,因此读者之间的队列没有锁争用。 (仅在读取器和写入器之间)您甚至可以通过将内容放入大于1的批处理队列中来优化此操作。
答案 1 :(得分:1)
正如Alexei所建议的那样,您可以创建OrderedTask:
class OrderedTask implements Comparable<OrderedTask> {
private final Integer index;
private final String line;
public OrderedTask(Integer index, String line) {
this.index = index;
this.line = line;
}
@Override
public int compareTo(OrderedTask o) {
return index < o.getIndex() ? -1 : index == o.getIndex() ? 0 : 1;
}
public Integer getIndex() {
return index;
}
public String getLine() {
return line;
}
}
作为输出队列,您可以使用自己的优先级队列支持:
class OrderedTaskQueue {
private final ReentrantLock lock;
private final Condition waitForOrderedItem;
private final int maxQueuesize;
private final PriorityQueue<OrderedTask> backedQueue;
private int expectedIndex;
public OrderedTaskQueue(int maxQueueSize, int startIndex) {
this.maxQueuesize = maxQueueSize;
this.expectedIndex = startIndex;
this.backedQueue = new PriorityQueue<>(2 * this.maxQueuesize);
this.lock = new ReentrantLock();
this.waitForOrderedItem = this.lock.newCondition();
}
public boolean put(OrderedTask item) {
ReentrantLock lock = this.lock;
lock.lock();
try {
while (this.backedQueue.size() >= maxQueuesize && item.getIndex() != expectedIndex) {
this.waitForOrderedItem.await();
}
boolean result = this.backedQueue.add(item);
this.waitForOrderedItem.signalAll();
return result;
} catch (InterruptedException e) {
throw new RuntimeException();
} finally {
lock.unlock();
}
}
public OrderedTask take() {
ReentrantLock lock = this.lock;
lock.lock();
try {
while (this.backedQueue.peek() == null || this.backedQueue.peek().getIndex() != expectedIndex) {
this.waitForOrderedItem.await();
}
OrderedTask result = this.backedQueue.poll();
expectedIndex++;
this.waitForOrderedItem.signalAll();
return result;
} catch (InterruptedException e) {
throw new RuntimeException();
} finally {
lock.unlock();
}
}
}
StartIndex 是第一个已排序任务的索引,并且 maxQueueSize 用于在我们等待一些较早的任务完成时停止处理其他任务(而不是填充内存)。它应为处理线程数的两倍/三倍,以免立即停止处理并具有可伸缩性。
然后您应该创建任务:
int indexOrder =0;
while ((line = reader.readLine()) != null) {
inputQueue.put(new OrderedTask(indexOrder++,line);
}
仅由于您的示例而使用逐行。您应该更改OrderedTask以支持这批生产线。
答案 2 :(得分:0)
为什么不逆流?