web-dev-qa-db-ja.com

Javaでの複数のファイルとその内容の解析、ExecutorServiceなしのマルチスレッドの使用

Javaで並行性を学び、OracleのWebサイトのチュートリアルを読みました。一部は理解しましたが、かなりの部分がわからなくなりました。架空の問題について考えていました(ただし、または複数のスレッドを使用するのは良いケースではありません)100個のテキストファイルがあり、それらすべてで特定のWordを検索する必要があります。ブロッキングキューを実装し、ThreadPoolとexecutorサービス:

  1. この問題を解決するにはどうすればよいですか(アルゴリズム的に考える)?
  2. BlockingQueueを複数のプロデューサーで実装したい場合はどうすればよいですか?put()100 textに100のスレッドがある複数のコンシューマーモデルを念頭に置いてください。 BlockingQueue内のファイルの内容と別の100からtake()を検索し、それらの中で特定の単語を検索しますか?

私が書いたものは意味があるかもしれないし、意味がないかもしれませんが、私は初心者であり、この問題はプログラミングのインタビューで頻繁に出てきたので、これについて詳しく知りたいと思っています。

3
Shehryar

すばらしい質問です。複数のファイル(各スレッドで1つのスレッドを読み取る)を読み取り、複数のスレッドでデータを処理する方法を示すために、小さな例(6つのスレッドしか使用しないが、簡単に拡張できます)を記述しました。

それでは、Controllerから始めましょう。これは基本的に、他のスレッドの作成と管理を担当するディレクターに過ぎません。各スレッドにキューへの参照を与え、スレッドが作業を実行できるようにすることに注意してください(アイテムの追加またはキューからの削除)。また、スレッドの2つのコレクション(プロデューサースレッド用とすべてのスレッド用)を保持していることにも気づくでしょう。プロデューサースレッドコレクションは、コンシューマースレッドがさらに入力を待つ必要があるかどうかを知る方法を提供するために使用されます。すべてのスレッドを保持するコレクションは、すべてのプロデューサーとコンシューマーが作業を完了する前にコントローラーが終了しないようにするために使用されます。

package multithreading.producer_consumer.blockingQueue;

import Java.nio.file.Paths;
import Java.util.ArrayList;
import Java.util.Collection;
import Java.util.concurrent.BlockingQueue;
import Java.util.concurrent.LinkedBlockingDeque;

public class Controller {

    private static final int NUMBER_OF_CONSUMERS = 3;
    private static final int NUMBER_OF_PRODUCERS = 3;
    private static final int QUEUE_SIZE = 2;
    private static BlockingQueue<String> queue;
    private static Collection<Thread> producerThreadCollection, allThreadCollection;

    public static void main(String[] args) {
        producerThreadCollection = new ArrayList<Thread>();
        allThreadCollection = new ArrayList<Thread>();
        queue = new LinkedBlockingDeque<String>(QUEUE_SIZE);

        createAndStartProducers();
        createAndStartConsumers();

        for(Thread t: allThreadCollection){
            try {
                t.join();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        System.out.println("Controller finished");
    }

    private static void createAndStartProducers(){
        for(int i = 1; i <= NUMBER_OF_PRODUCERS; i++){
            Producer producer = new Producer(Paths.get("./src/multithreading/producer_consumer/blockingQueue/file"+i+".txt"), queue);
            Thread producerThread = new Thread(producer,"producer-"+i);
            producerThreadCollection.add(producerThread);
            producerThread.start();
        }
        allThreadCollection.addAll(producerThreadCollection);
    }

    private static void createAndStartConsumers(){
        for(int i = 0; i < NUMBER_OF_CONSUMERS; i++){
            Thread consumerThread = new Thread(new Consumer(queue), "consumer-"+i);
            allThreadCollection.add(consumerThread);
            consumerThread.start();
        }
    }

    public static boolean isProducerAlive(){
        for(Thread t: producerThreadCollection){
            if(t.isAlive())
                return true;
        }
        return false;
    }
}

次に、Producerクラスのコードを示します。このコードを使用して、単一のファイルをそれぞれ読み取ることを目的とするすべてのスレッドを作成します。プロデューサーがputメソッドを使用して使用可能なスペースがあるため、特定のファイルを1行ずつ読み取り、それらの行をキューに追加することがわかります。

package multithreading.producer_consumer.blockingQueue;

import Java.io.BufferedReader;
import Java.io.IOException;
import Java.nio.file.Files;
import Java.nio.file.Path;
import Java.util.concurrent.BlockingQueue;

public class Producer implements Runnable{

    private Path fileToRead;
    private BlockingQueue<String> queue;

    public Producer(Path filePath, BlockingQueue<String> q){
        fileToRead = filePath;
        queue = q;
    }

    @Override
    public void run() {
        try {
            BufferedReader reader = Files.newBufferedReader(fileToRead);
            String line;
            while((line = reader.readLine()) != null){
                try {
                    queue.put(line);
                    System.out.println(Thread.currentThread().getName() + " added \"" + line + "\" to queue, queue size: " + queue.size());             
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

            System.out.println(Thread.currentThread().getName()+" finished");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

最後に、キューからデータを読み取って適切に処理するConsumerクラスを次に示します。このクラスはtakeメソッドを使用しないことに注意してください。プログラムをすべてのファイルの処理後に終了するように、私はこのように書いた。消費者を存続させたい場合は、polltakeで置き換えることができます(runを渡すなどのInterruptedExceptionメソッドへのいくつかの他のマイナーな調整とともに) takeが値を返すのを待つ間に発生する可能性があります)。

package multithreading.producer_consumer.blockingQueue;

import Java.util.concurrent.BlockingQueue;

public class Consumer implements Runnable{
    private BlockingQueue<String> queue;

    public Consumer(BlockingQueue<String> q){
        queue = q;
    }

    public void run(){
        while(true){
            String line = queue.poll();

            if(line == null && !Controller.isProducerAlive())
                return;

            if(line != null){
                System.out.println(Thread.currentThread().getName()+" processing line: "+line);
                //Do something with the line here like see if it contains a string
            }

        }
    }
}

ここに私が使用した3つの入力ファイルがあります:

file1.txt

file #1 line 1
file #1 line 2
file #1 line 3
file #1 line 4
file #1 line 5

file2.txt

This is file #2 line 1
This is file #2 line 2
This is file #2 line 3
This is file #2 line 4
This is file #2 line 5

file3.txt

Lastly we have file #3 line 1
Lastly we have file #3 line 2
Lastly we have file #3 line 3
Lastly we have file #3 line 4
Lastly we have file #3 line 5

これは、プログラムからの出力例です。 System.out.printlnが同期されていないため、出力が順番になっていないことに注意してください。

consumer-0 processing line: Lastly we have file #3 line 1
consumer-0 processing line: This is file #2 line 1
producer-2 added "This is file #2 line 1" to queue, queue size: 1
producer-2 added "This is file #2 line 2" to queue, queue size: 1
producer-2 added "This is file #2 line 3" to queue, queue size: 1
producer-2 added "This is file #2 line 4" to queue, queue size: 2
consumer-1 processing line: file #1 line 1
consumer-1 processing line: This is file #2 line 4
consumer-1 processing line: This is file #2 line 5
producer-1 added "file #1 line 1" to queue, queue size: 1
producer-1 added "file #1 line 2" to queue, queue size: 0
producer-3 added "Lastly we have file #3 line 1" to queue, queue size: 0
producer-1 added "file #1 line 3" to queue, queue size: 1
consumer-1 processing line: file #1 line 2
producer-2 added "This is file #2 line 5" to queue, queue size: 0
producer-1 added "file #1 line 4" to queue, queue size: 2
producer-2 finished
consumer-2 processing line: This is file #2 line 3
consumer-2 processing line: Lastly we have file #3 line 2
consumer-2 processing line: file #1 line 4
consumer-2 processing line: file #1 line 5
consumer-0 processing line: This is file #2 line 2
producer-1 added "file #1 line 5" to queue, queue size: 0
producer-1 finished
consumer-1 processing line: file #1 line 3
producer-3 added "Lastly we have file #3 line 2" to queue, queue size: 2
producer-3 added "Lastly we have file #3 line 3" to queue, queue size: 1
producer-3 added "Lastly we have file #3 line 4" to queue, queue size: 1
producer-3 added "Lastly we have file #3 line 5" to queue, queue size: 0
producer-3 finished
consumer-0 processing line: Lastly we have file #3 line 3
consumer-2 processing line: Lastly we have file #3 line 5
consumer-1 processing line: Lastly we have file #3 line 4
Controller finished

これが、ExecutorServiceを使用せずにタスクを実行する方法を示すのに役立つことを願っています。楽しんで!

3
D.B.