1. 序章

このチュートリアルでは、2つのスレッドを使用して偶数と奇数を印刷する方法を見ていきます。

目標は、番号を順番に印刷することですが、一方のスレッドは偶数のみを印刷し、もう一方のスレッドは奇数のみを印刷します。 この問題を解決するために、スレッド同期とスレッド間通信の概念を使用します。

2. Javaのスレッド

スレッドは、同時に実行できる軽量プロセスです。 複数のスレッドを同時に実行すると、並列で実行されているさまざまなスレッドを介して一度に複数のタスクを処理できるため、パフォーマンスとCPU使用率の点で優れている場合があります。

Javaのスレッドの詳細については、この記事を参照してください。

Javaでは、Threadクラスを拡張するか、Runnableインターフェースを実装することにより、スレッドを作成できます。 どちらの場合も、 run メソッドをオーバーライドし、その中にスレッドの実装を記述します。

これらのメソッドを使用してスレッドを作成する方法の詳細については、ここを参照してください。

3. スレッドの同期

マルチスレッド環境では、2つ以上のスレッドがほぼ同時に同じリソースにアクセスしている可能性があります。 これは致命的であり、誤った結果につながる可能性があります。 これを防ぐには、特定の時点で1つのスレッドのみがリソースにアクセスするようにする必要があります。

これは、スレッド同期を使用して実現できます。

Javaでは、メソッドまたはブロックを同期済みとしてマークできます。つまり、特定の時点でそのメソッドまたはブロックに入ることができるのは1つのスレッドだけです。

Javaでのスレッド同期の詳細については、こちらを参照してください。

4. スレッド間通信

スレッド間通信により、同期されたスレッドは一連のメソッドを使用して相互に通信できます。

使用されるメソッドは、 wait notify、、および notifyAll、であり、これらはすべてObjectクラスから継承されます。

Wait()により、現在のスレッドは、他のスレッドが同じオブジェクトでnotify()またはnotifyAll()を呼び出すまで無期限に待機します。 notify()を呼び出して、このオブジェクトのモニターへのアクセスを待機しています。

これらのメソッドの動作の詳細については、ここを参照してください。

5. 奇数と偶数を交互に印刷する

5.1. wait()および notify()を使用する

説明した同期とスレッド間通信の概念を使用して、2つの異なるスレッドを使用して奇数と偶数を昇順で出力します。

最初のステップでは、Runnableインターフェースを実装して、両方のスレッドのロジックを定義します run メソッドでは、数値が偶数か奇数かを確認します。

数値が偶数の場合は、PrinterクラスのprintEvenメソッドを呼び出します。それ以外の場合は、printOddメソッドを呼び出します。

class TaskEvenOdd implements Runnable {
    private int max;
    private Printer print;
    private boolean isEvenNumber;

    // standard constructors

    @Override
    public void run() {
        int number = isEvenNumber ? 2 : 1;
        while (number <= max) {
            if (isEvenNumber) {
                print.printEven(number);
            } else {
                print.printOdd(number);
            }
            number += 2;
        }
    }
}

Printerクラスを次のように定義します。

class Printer {
    private volatile boolean isOdd;

    synchronized void printEven(int number) {
        while (!isOdd) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        System.out.println(Thread.currentThread().getName() + ":" + number);
        isOdd = false;
        notify();
    }

    synchronized void printOdd(int number) {
        while (isOdd) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        System.out.println(Thread.currentThread().getName() + ":" + number);
        isOdd = true;
        notify();
    }
}

mainメソッドでは、定義されたクラスを使用して2つのスレッドを作成します。 Printer クラスのオブジェクトを作成し、パラメーターとしてTaskEvenOddに渡します。コンストラクタ:

public static void main(String... args) {
    Printer print = new Printer();
    Thread t1 = new Thread(new TaskEvenOdd(print, 10, false),"Odd");
    Thread t2 = new Thread(new TaskEvenOdd(print, 10, true),"Even");
    t1.start();
    t2.start();
}

最初のスレッドは奇数スレッドになるため、パラメーターisEvenNumberの値としてfalseを渡します。 2番目のスレッドでは、代わりにtrueを渡します。 両方のスレッドでmaxValueを10に設定し、1から10までの数字のみが出力されるようにします。

次に、 start()メソッドを呼び出して、両方のスレッドを開始します。 これにより、上記で定義した両方のスレッドの run()メソッドが呼び出され、数値が奇数か偶数かを確認して出力します。

奇数スレッドが実行を開始すると、変数numberの値は1になります。 maxValue 未満であり、フラグ isEvenNumber がfalseであるため、 printOdd()が呼び出されます。 メソッドでは、フラグかどうかを確認します isOdd 真実であり、真実である間、私たちは待つ()。 以来 isOdd 最初はfalse、 待つ() は呼び出されず、値が出力されます。

次に、isOddの値をtrueに設定して、奇数スレッドが待機状態になり、notify()を呼び出して偶数スレッドをウェイクアップします。 odd フラグが偽であるため、偶数スレッドが起動して偶数を出力します。 次に、 notify()を呼び出して、奇数スレッドをウェイクアップします。

変数numberの値がmaxValueより大きくなるまで、同じプロセスが実行されます。

5.2. セマフォの使用

セマフォは、カウンターを使用して共有リソースへのアクセスを制御します。 カウンターがゼロより大きい場合、アクセスが許可されます。 ゼロの場合、アクセスは拒否されます。

Javaは、java.util.concurrentパッケージでSemaphoreクラスを提供し、これを使用して説明されたメカニズムを実装できます。 セマフォの詳細については、こちらをご覧ください。

奇数スレッドと偶数スレッドの2つのスレッドを作成します。 奇数スレッドは1から始まる奇数を印刷し、偶数スレッドは2から始まる偶数を印刷します。

両方のスレッドには、SharedPrinterクラスのオブジェクトがあります。 SharedPrinterクラスには、semOddとsemEvenの2つのセマフォがあり、で始まる許可が1と0になります。 これにより、奇数が最初に印刷されます。

2つの方法があります printEvenNum() と printOddNum()。 奇妙なスレッドは printOddNum() メソッドと偶数スレッドは printEvenNum() 方法。

奇数を出力するには、 acquire()メソッドを semOdd で呼び出し、最初の許可が1であるため、アクセスを正常に取得し、奇数を出力して[X188X ] semEven。のrelease()

release()を呼び出すと、 semEven の許可が1つ増え、偶数スレッドはアクセスを正常に取得して偶数を出力できます。

これは、上記のワークフローのコードです。

public static void main(String[] args) {
    SharedPrinter sp = new SharedPrinter();
    Thread odd = new Thread(new Odd(sp, 10),"Odd");
    Thread even = new Thread(new Even(sp, 10),"Even");
    odd.start();
    even.start();
}
class SharedPrinter {

    private Semaphore semEven = new Semaphore(0);
    private Semaphore semOdd = new Semaphore(1);

    void printEvenNum(int num) {
        try {
            semEven.acquire();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println(Thread.currentThread().getName() + num);
        semOdd.release();
    }

    void printOddNum(int num) {
        try {
            semOdd.acquire();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println(Thread.currentThread().getName() + num);
        semEven.release();

    }
}

class Even implements Runnable {
    private SharedPrinter sp;
    private int max;

    // standard constructor

    @Override
    public void run() {
        for (int i = 2; i <= max; i = i + 2) {
            sp.printEvenNum(i);
        }
    }
}

class Odd implements Runnable {
    private SharedPrinter sp;
    private int max;

    // standard constructors 
    @Override
    public void run() {
        for (int i = 1; i <= max; i = i + 2) {
            sp.printOddNum(i);
        }
    }
}

6. 結論

このチュートリアルでは、Javaで2つのスレッドを使用して、奇数と偶数を交互に出力する方法を確認しました。 同じ結果を達成するための2つのメソッドを調べました。wait()とnotify()を使用し、セマフォを使用します。

そして、いつものように、完全に機能するコードはGitHub利用できます。