1前書き

この記事では、Javaの最も基本的なメカニズムの1つであるスレッド同期について説明します。

最初に、いくつかの重要な同時実行関連の用語と方法論について説明します。

そして、

wait()

と__notify()をよりよく理解することを目的として、並行性の問題に対処する簡単なアプリケーションを開発します。

** 2 Javaでのスレッド同期

マルチスレッド環境では、複数のスレッドが同じリソースを変更しようとします。スレッドが適切に管理されていない場合、もちろんこれは一貫性の問題につながります。


2.1. Javaで保護されたブロック

Javaで複数のスレッドの動作を調整するために使用できるツールの1つに、保護ブロックがあります。そのようなブロックは、実行を再開する前に特定の条件をチェックし続けます。

それを念頭に置いて、私たちは利用するつもりです:



  • Object.wait()

    • スレッドを中断する

  • __https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify–[Object.notify()]

–__スレッドを起こす

これは、

Thread

のライフサイクルを表す次の図からよりよく理解できます。

リンク:/uploads/Java



Wait

and

Notify-768×399.png%20768w[]

このライフサイクルを制御する方法はたくさんあります。ただし、この記事では、

wait()

と__notify()にのみ焦点を当てます。


3

wait()

メソッド

簡単に言うと、

wait()–

を呼び出すと、他のスレッドが同じオブジェクトに対して

notify()

または

notifyAll()

を呼び出すまで現在のスレッドを待機させます。

そのためには、現在のスレッドがオブジェクトのモニタを所有している必要があります。

Javadocs

によると、これは次の場合に起こります。

  • 与えられたオブジェクトに対して

    synchronized

    インスタンスメソッドを実行しました

  • 与えられたオブジェクトに対して

    synchronized

    ブロックの本体を実行しました


  • Class

    型のオブジェクトに対して

    synchized static

    メソッドを実行する

一度に1つのアクティブスレッドだけがオブジェクトのモニタを所有できることに注意してください。

この__wait()メソッドには3つのオーバーロードシグネチャが付属しています。これらを見てみましょう。


3.1.

待つ()



wait()メソッドは、別のスレッドがこのオブジェクトに対して

notify()

または

notifyAll()__を呼び出すまで、現在のスレッドを無期限に待機させます。


3.2.

wait(長いタイムアウト)


このメソッドを使用して、スレッドが自動的に起動するまでのタイムアウトを指定できます。

notify()

または__notifyAll()を使用して、タイムアウトに達する前にスレッドを起こすことができます。


wait(0)

を呼び出すことは、__wait()を呼び出すことと同じです。


3.3.

wait(長いタイムアウト、int nanos)


これは同じ機能を提供するもう1つのシグネチャですが、唯一の違いはより高い精度を提供できることです。

合計タイムアウト期間(ナノ秒単位)は、

1

000

000 ** timeout nanos.

として計算されます。



4. notify()

および

notifyAll()



notify()

メソッドは、このオブジェクトのモニタへのアクセスを待っているスレッドを起こすために使用されます。

待機中のスレッドに通知する方法は2つあります。


4.1.

notify()




wait()

メソッドのいずれかを使用して)このオブジェクトのモニターを待機しているすべてのスレッドに対して、

notify()

メソッドは任意のスレッドに任意にウェイクアップするよう通知します。実装によって異なります。


notify()

は単一のランダムスレッドを起こすので、スレッドが同様のタスクを行っている場合に相互排他的ロックを実装するために使用できますが、ほとんどの場合、

notifyAll()

を実装する方が現実的です。


4.2.

notifyAll()


このメソッドは、このオブジェクトのモニターを待っているすべてのスレッドを単に起こします。

目覚めたスレッドは他のスレッドと同じように通常の方法で完了します。

しかし、実行を継続させる前に、常に

スレッドを進めるために必要な条件のクイックチェックを定義してください

– スレッドが通知を受け取らずに目覚めた場合があるかもしれません例)


5送信者と受信者の同期問題

基本を理解したので、次に

wait()

メソッドと

notify()

メソッドを使用してそれらの間の同期を設定する簡単な

Sender



Receiver

アプリケーションを見てみましょう。


  • Sender



    Receiver

    にデータパケットを送信することになっています


  • Receiver



    Sender

    になるまで、

    Receiver

    はデータパケットを処理できません。

送信済み
** 同様に、

Sender

は別のパケットを送信しようとしないでください。


Receiver

はすでに前のパケットを処理しました

まず、

Sender

から

Receiverに送信されるデータ

packet

で構成される

Data

クラスを作成しましょう。

wait()



notifyAll()__を使用して、それらの間の同期を設定します。

public class Data {
    private String packet;

   //True if receiver should wait
   //False if sender should wait
    private boolean transfer = true;

    public synchronized void send(String packet) {
        while (!transfer) {
            try {
                wait();
            } catch (InterruptedException e)  {
                Thread.currentThread().interrupt();
                Log.error("Thread interrupted", e);
            }
        }
        transfer = false;

        this.packet = packet;
        notifyAll();
    }

    public synchronized String receive() {
        while (transfer) {
            try {
                wait();
            } catch (InterruptedException e)  {
                Thread.currentThread().interrupt();
                Log.error("Thread interrupted", e);
            }
        }
        transfer = true;

        notifyAll();
        return packet;
    }
}

ここで何が起こっているのかを説明しましょう。


  • packet

    変数は、転送されているデータを表します。

ネットワーク
**

boolean

変数

transfer –

を持っています。


Receiver

は同期に使用します。

  • ** この変数が

    true

    であるなら、

    Receiver

    は待つべきです

メッセージを送信するための

Sender



それが

false

の場合、

Sender



Receiver

が受信するのを待つべきです

メッセージ
**

Sender



send()

メソッドを使用して

Receiver

にデータを送信します。

  • **

    transfer



    falseの場合は、

    wait()を呼び出して待機します。




しかし、それが

true

のときは、ステータスを切り替え、メッセージを設定して呼び出し

重要なことを指定するために他のスレッドを起動するための

notifyAll()

イベントが発生し、実行を継続できるかどうかを確認できます。
** 同様に、

Receiver



receive()

メソッドを使用します。

  • **

    Sender

    によって

    transfer



    false

    に設定されている場合、それだけ

それ以外の場合は、このスレッドで

wait()

を呼び出します。


条件が満たされると、ステータスを切り替え、待機中のすべてに通知します

ウェイクアップして

Receiver

であったデータパケットを返すスレッド


5.1.

wait()



while

ループで囲むのはなぜですか?


notify()



notifyAll()

は、このオブジェクトのモニタを待機しているスレッドをランダムに起動しますので、条件が満たされることは必ずしも重要ではありません。時々、スレッドが目覚めていることが起こるかもしれませんが、条件はまだ実際には満たされていません。

また、誤ったウェイクアップから通知を受け取ることなくスレッドを待機状態からウェイクアップさせることができるチェックを定義することもできます。


5.2. s

__end()


メソッドと

receive()__メソッドを同期させる必要があるのはなぜですか?

組み込みロックを提供するために、これらのメソッドを

synchronized

メソッド内に配置しました。

wait()

メソッドを呼び出すスレッドが固有のロックを所有していない場合は、エラーがスローされます。

これらのインスタンスがスレッドによって実行されるように、

Sender



Receiver

を作成し、両方に

Runnable

インターフェイスを実装します。

まず、

Sender

がどのように機能するのかを見てみましょう。

public class Sender implements Runnable {
    private Data data;

   //standard constructors

    public void run() {
        String packets[]= {
          "First packet",
          "Second packet",
          "Third packet",
          "Fourth packet",
          "End"
        };

        for (String packet : packets) {
            data.send(packet);

           //Thread.sleep() to mimic heavy server-side processing
            try {
                Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
            } catch (InterruptedException e)  {
                Thread.currentThread().interrupt();
                Log.error("Thread interrupted", e);
            }
        }
    }
}

この

Sender

の場合:

  • 複数のランダムデータパケットを作成します


packets[]

配列内のネットワーク
** パケットごとに、単に

send()

と呼びます。

  • 次に、ランダムな間隔で

    Thread.sleep()

    を呼び出して、再現します

サーバーサイドの重い処理

最後に、

Receiver

を実装しましょう。

public class Receiver implements Runnable {
    private Data load;

   //standard constructors

    public void run() {
        for(String receivedMessage = load.receive();
          !"End".equals(receivedMessage);
          receivedMessage = load.receive()) {

            System.out.println(receivedMessage);

           //...
            try {
                Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                Log.error("Thread interrupted", e);
            }
        }
    }
}

ここでは、最後の

“ End”

データパケットを取得するまで、ループ内で

load.receive()

を呼び出すだけです。

では、このアプリケーションの動作を見てみましょう。

public static void main(String[]args) {
    Data data = new Data();
    Thread sender = new Thread(new Sender(data));
    Thread receiver = new Thread(new Receiver(data));

    sender.start();
    receiver.start();
}

次のような出力が表示されます。

First packet
Second packet
Third packet
Fourth packet

そして、ここにいます – ** すべてのデータパケットを正しい順番で受信し、送信側と受信側の間で正しい通信が確立されました。


6. 結論

この記事では、Javaにおけるいくつかのコア同期の概念について説明しました。より具体的には、興味深い同期問題を解決するために

wait()



notify()

を使用する方法に焦点を当てました。そして最後に、これらの概念を実際に適用したコードサンプルを調べました。

ここで説明する前に、

wait()



notify()

、および

notifyAll()

などのこれらの低レベルAPIはすべて、従来どおりに機能する方法であることに注意する必要があります。 Javaのネイティブの

Lock

および

Condition

インターフェース(

java.util.concurrent.locks

パッケージで利用可能)などが優れています。


java.util.concurrent

パッケージの詳細については、リンク:/java-util-concurrent[java.util.concurrentの概要]の記事を参照してください。また、

Lock



Condition

については、リンク:/java-concurrent-locksで説明されています。[java.util.concurrent.Locksのご案内、ここ]

いつものように、この記事で使用されている完全なコード・スニペットは入手可能です。

https://github.com/eugenp/tutorials/tree/master/core-java-concurrency

[over
GitHubで。