web-dev-qa-db-ja.com

Java)でスレッド制限を作成する方法

読み取るファイルが1000個あり、いくつかの制限があるため、最大5個のファイルを並行して読み取りたいとします。そして、そのうちの1つが終了したらすぐに、新しいものを開始したいと思います。

ファイルのリストを持っているmain関数があり、1つのスレッドが終了するたびにカウンターを変更しようとしています。しかし、それは機能しません!

なにか提案を?

以下は主な関数ループです

for (final File filename : folder.listFiles()) {

    Object lock1 = new Object();
    new myThread(filename, lock1).start();
    counter++;
    while (counter > 5);
}
13
Afshin Moazami

このようなスレッドを生成することは、進むべき道ではありません。 ExecutorServiceを使用し、プールを5に指定します。すべてのファイルをBlockingQueueまたは別のスレッドセーフコレクションなどに配置すると、実行中のすべてのファイルはpoll()になります。意のままに。

public class ThreadReader {

    public static void main(String[] args) {
        File f = null;//folder
        final BlockingQueue<File> queue = new ArrayBlockingQueue<File>(1000);
        for(File kid : f.listFiles()){
            queue.add(kid);
        }

        ExecutorService pool = Executors.newFixedThreadPool(5);

        for(int i = 1; i <= 5; i++){
            Runnable r = new Runnable(){
                public void run() {
                    File workFile = null;
                    while((workFile = queue.poll()) != null){
                        //work on the file.
                    }
                }
            };
            pool.execute(r);
        }
    }
}
22
Kylar

ExecutorServiceをスレッドプールおよびキューとして使用できます。

ExecutorService pool = Executors.newFixedThreadPool(5);
File f = new File(args[0]);

for (final File kid : f.listFiles()) {
    pool.execute(new Runnable() {
        @Override
        public void run() {
            process(kid);
        }
    });
}
pool.shutdown();
// wait for them to finish for up to one minute.
pool.awaitTermination(1, TimeUnit.MINUTES);
3
Peter Lawrey

Kylarの答えのアプローチは正しいものです。スレッドプールを最初から実装するのではなく、Javaクラスライブラリによって提供されるエグゼキュータクラスを使用します(ひどく)。


しかし、私はあなたの質問のコードとそれが機能しない理由を議論することが役立つかもしれないと思いました。 (私はあなたが私ができる限り省略した部分のいくつかを埋めました...)

_public class MyThread extends Thread {

    private static int counter;

    public MyThread(String fileName, Object lock) {
        // Save parameters in instance variables
    }

    public void run() {
        // Do stuff with instance variables
        counter--;
    }

    public static void main(String[] args) {
        // ...
        for (final File filename : folder.listFiles()) {
            Object lock1 = new Object();
            new MyThread(filename, lock1).start();
            counter++;
            while (counter > 5);
        }
        // ...
    }
}
_

さて、これの何が問題になっていますか?なぜそれが機能しないのですか?

最初の問題は、mainで同期を行わずにcounterを読み書きしていることです。ワーカースレッドによっても更新されていると思います。それ以外の場合、コードは意味がありません。つまり、メインスレッドには、子スレッドによって行われた更新の結果が表示されない可能性が高いということです。言い換えると、while (counter > 5);は無限ループになる可能性があります。 (実際、これはかなり可能性が高いです。JITコンパイラーは、_counter > 5_が前の_counter++;_ステートメントの後にレジスターに残っているcounterの値をテストするだけのコードを生成できます。

2番目の問題は、while (counter > 5);ループがリソースを非常に浪費していることです。あなたはJVMに変数をポーリングするように指示しています...そしてそれはこれを1秒間に数十億回実行する可能性があります... 1つのプロセッサ(コア)を完全に実行します。あなたはそれをすべきではありません。低レベルのプリミティブを使用してこの種のものを実装する場合は、JavaのObject.wait()およびObject.notify()メソッドを使用する必要があります。例えばメインスレッドは待機し、各ワーカースレッドが通知します。

3
Stephen C

新しいスレッドを作成するために使用している方法が何であれ、グローバルカウンターをインクリメントし、スレッドの作成の前後に条件ステートメントを追加します。制限に達した場合は、新しいスレッドを作成しないでください。ファイルをキュー(リスト)にプッシュする可能性があります。 ?)次に、スレッドが作成された後、キューにアイテムがある場合は、それらのアイテムを最初に処理するために、別の条件ステートメントを追加できます。

0
Ozzy