Javaマルチスレッド学習3(スレッド間の共有とコラボレーション)

目次

           ●yield()メソッドとjoin()メソッドの概要

           ●スレッド間の共有

           ●スレッド間のコラボレーション

yield()メソッドとjoin()メソッドの概要

            Yield()メソッド:現在のスレッドにCPU所有権を放棄させます(スレッドは放棄します)が、yield時間を設定できません。ロックリソースは解放されません、そして収率を実行するすべてのスレッド()が実行可能な状態に入った直後に実行されてもよいです。

            join()メソッド:指定されたスレッドを現在のスレッド(スレッドジャンパー)に追加します。交互に実行される2つのスレッドを順次実行スレッドにマージできます。たとえば、スレッドAのjoin()メソッドがスレッドBで呼び出され、スレッドAが実行を終了するまでスレッドBは実行を継続しません。

スレッド間の共有

            Javaは、複数のスレッドをサポートして、同じオブジェクトまたはオブジェクトのメンバー変数に同時にアクセスします。キーワードsynchronizedは、メソッドを変更したり、同期ブロックの形式で使用したりできます。主に、複数のスレッドが1つのスレッドのみを持つことができるようにします。同時にメソッド。または同期ブロックでは、組み込みロックメカニズムとも呼ばれる変数へのスレッドアクセスの可視性と排他性が保証されます

            オブジェクトロックとクラスロック:オブジェクトロックは、オブジェクトインスタンスメソッドまたはオブジェクトインスタンスで使用されます。クラスロックは、クラスの静的メソッドまたはクラスのクラスオブジェクトに使用されます。クラスには多くのオブジェクトインスタンスが存在する可能性がありますが、各クラスには1つのクラスオブジェクトしかないため、異なるオブジェクトインスタンスのオブジェクトロックは相互に干渉しませんが、クラスごとに1つのクラスロックしかありません。

            :クラスロックは概念的なものであり、実際のものではありません。クラスロックは、実際には各クラスの対応するクラスオブジェクトをロックします。クラスロックとオブジェクトロックも相互に干渉しません。

            オブジェクトロックの使用

/**
 * @ author xiaozhou
 * @ Date 2019/4/8
 */
public class SynTest {

    private long count = 0;
    private Object obj = new Object();   //作为一个锁

    /**
     * 下面的三种写法效果是一样的
     */
    public void incCount1() {
        synchronized (obj) {
            //业务逻辑
        }
        
    }

    /**
     * 锁对象
     * 锁的是当前类的实例this
     */
    public synchronized void incCount2() {
         //业务逻辑
    }

    public void incCount3() {
        synchronized (this) {
            //业务逻辑
        }
    }

}

             クラスロックの使用:

    /**
     * 类锁,实际是锁类的class对象
     */
    private static synchronized void SynClass() {
        SleepTools.second(1);
        System.out.println("synClass going...");
        SleepTools.second(1);
        System.out.println("synClass end...");
    }

    private static Object obj = new Object();
    //因为obj是静态的,在全虚拟机只有一份,所以这里类似于类锁
    private void synStaticObject() {
        synchronized (obj) {   // 类似于类锁
            SleepTools.second(1);
            System.out.println("synClass going...");
            SleepTools.second(1);
            System.out.println("synClass end...");
        }
    }

            より詳細なコメントがコードに追加されたので、これ以上説明しません。

スレッド間のコラボレーション

            スレッドは互いに協力して、次のような特定のジョブを完了します。1つのスレッドがオブジェクトの値を変更し、別のスレッドが変更を認識して、対応する操作を実行します。プロセス全体が1つのスレッドで開始され、最終的な実行は次のようになります。別の1つのスレッド。前者はプロデューサー、後者はコンシューマーです。このモデルは「what」と「how」(How)を分離します。簡単な方法は、コンシューマースレッドを継続的にループさせて、変数が期待値を満たしているかどうかを確認することです。ループ、および条件が満たされると、whileループが終了し、コンシューマーの作業が完了します。(生産者と消費者の理解については、自分で記事を書くつもりでしたが、生産者・消費者問題を完全に理解できる記事を見つけ後は、他人の理解も十分ではないと思います。私が書いた場合この記事は他の人によってうまく書かれていないので、ここのすべての人にそれをお勧めします、そして私はそれを自分で学びました。)しかし私は生産者/消費者モデルには2つの問題があると思います:

           (1)適時性の確保が難しい

           (2)経費の削減が難しい。消費者が状態の変化をより迅速に見つけることができるように、1ミリ秒のスリープなどのスリープ時間を短縮すると、より多くのプロセッサリソースを消費し、不当な浪費につながる可能性があります。

待機/通知メカニズム(待機/通知/通知すべて)

           待機/通知とは、スレッドAがオブジェクトOのwait()メソッドを呼び出して待機状態になり、別のスレッドBがオブジェクトOのnotify()またはnotifyAll()メソッドを呼び出し、スレッドAがオブジェクトOから通知を受信することを意味します。 。wait()メソッドが戻り、後続の操作が実行されます。上記の2つのスレッドは、オブジェクトOを介して対話を完了し、オブジェクトのwait()とnotify()/ notifyAll()の関係は、待機側と通知側の間の対話作業を完了するためのスイッチ信号のようなものです。

          1.待つ()

              このメソッドを呼び出すスレッドは待機状態になります。別のスレッドの通知を待つか、中断された場合にのみ戻ります。このメソッドを呼び出す前に、スレッドはオブジェクトのオブジェクトモニターロックを取得する必要があるため、次の場合にのみ使用できます。synchronizedメソッドまたは同期コード・ブロックは、待機()メソッドを呼び出します。wait()メソッドを呼び出した後、現在のスレッドはオブジェクトのロックを解放します。

              待機(長いタイムアウト)

               タイムアウトは一定期間待機します。パラメータはミリ秒です。つまり、着信パラメータのミリ秒を待機した後、通知または中断を待機しない場合は、タイムアウトして戻ります。

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

               タイムアウト期間をより正確に制御するために、2番目のパラメーターはナノ秒のオーダーです。

          2.通知()

              オブジェクトの通知を待機しているスレッドの1つを任意に選択して、wait()メソッドを呼び出すスレッドが待機状態から準備完了状態に切り替わり、ロックを再度取得する機会を待機するようにします。これにより、待機を呼び出すスレッドが待機状態になります。 ()メソッドはwait()メソッドで終了できます。notify()を呼び出した後、現在のスレッドはオブジェクトロックをすぐには解放しませんが、プログラムが同期ブロックの実行を終了するまでロックを解放しません。ロックを取得していないスレッドは、再び待機状態になります。

          3. notifyAll()

              オブジェクトで待機しているすべてのスレッドを待機状態から終了させ、実行可能状態にします。

 

         以下では、待機/通知の使用法を示すためにケースを使用します。

          ケース:2つのスレッド、1つのスレッドは1-52の値を出力し、もう1つのスレッドはAZの値を出力します。

                     印刷形式は次のとおりです。12A34B56C78D.......。

          まず、分析してみましょう。ケースの要件に応じて、2つのスレッドの一方が数字を印刷し、もう一方が文字AZを印刷します。さて、それを段階的に実装しましょう:

          1.最初に、番号のスレッド実装を出力します。

public class Number implements Runnable {

        private Object object;

        public Number(Object obj) {
            this.object = obj;
        }

        @Override
        public void run() {
            synchronized (object) {
                for (int i = 1; i < 53; i++) {
                    //代码块1
                    if (i >= 1 && i % 2 == 1) {
                        System.out.print(" "+i);
                    }
                    //代码块2
                    if (i % 2 == 0) {
                        System.out.print(i);
                        object.notifyAll();
                        try {
                            object.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

              このコードで説明する必要があるのは、コメントの2つのコードブロックだと思います。以下の説明では、印刷された12Aを例として取り上げます。

              コードブロック1は、出力1の論理的な判断であり、後で印刷される3、5などをこの条件で修飾できます。

              コードブロック2は、出力2を論理的に判断するためのものです。これは、2で割り切れる数の出力です。出力後、別のスレッドに文字Aを印刷するように通知する必要があるため、このスレッドはwait()メソッドを呼び出して、別のスレッドが文字Aの出力操作を終了するのを待ち、通知を発行してから印刷を続行します。必要に応じて番号。

             2.次に、文字を出力する別のスレッドの実装を見てみましょう。

public class CharacterThread implements Runnable {

    Object object;

    public CharacterThread(Object obj) {
        this.object = obj;
    }

    @Override
    public void run() {
        synchronized (object) {
            for (char i = 'A'; i <= 'Z'; i++) {
                System.out.print(i);
                object.notifyAll();
                try {
                    if (i < 'Z'){
                        object.wait();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

            forループでは、最初に各文字が印刷され、印刷が完了した後、次の番号を作成するために、番号を印刷するスレッドに通知する必要があります。そのため、文字を印刷した後、現在のスレッドは当分の間文字を印刷し続けることができず、数字を印刷するスレッドが印刷を続行するための通知を送信するまで待つ必要があります。もちろん、これらのロジックはi <'Z'に基づいています。

            3.次に、Main()メソッドで呼び出します。

public class PrintInfoDemo3 {
    public static void main(String[] args) {
        Object obj = new Object();
        new Thread(new NumberThread(obj)).start();
        new Thread(new CharacterThread(obj)).start();
    }
}

            演算結果:

   wait()メソッドがロックを解放し、notify()がロックを解放しないことを証明します

   1. wait()メソッドがロックを解放します。

public class WaitThread implements Runnable {

        private Object obj;

        public WaitThread(Object object) {
            this.obj = object;
        }

        @Override
        public void run() {
            synchronized (obj) {
                try {
                    System.out.println(Thread.currentThread().getName() + "Begin wait()");
                    obj.wait();
                    System.out.println(Thread.currentThread().getName() + "End wait()");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

       main()メソッドで呼び出されます

 public static void main(String[] args) {
        Object object = new Object();
        WaitThread waitThread1 = new WaitThread(object);
        WaitThread waitThread2 = new WaitThread(object);
        new Thread(waitThread1).start();
        new Thread(waitThread2).start();
    }

操作の結果は次のとおりです。

wait()メソッドがロックを解除しない場合、Thread-0は同期コードブロックに入ってprintステートメントを実行しません。したがって、wait()メソッドがロックを解除することが証明されています。

2. notify()メソッドがロックを解放しないことの証明

    run()メソッドのロジックを書き換えるための新しいスレッドを作成します

public class NotifyThread1 implements Runnable {

        private Object obj;

        public NotifyThread1(Object object) {
            this.obj = object;
        }

        @Override
        public void run() {
            synchronized (obj) {
                try {
                    System.out.println("Begin wait(),ThreadName = " + Thread.currentThread().getName());
                    obj.wait();
                    System.out.println("End wait(), ThreadName = " + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

      run()メソッドのロジックを書き直すための新しいスレッドを作成します

 public class NotifyThread2 implements Runnable {

        private Object obj;

        public NotifyThread2(Object object) {
            this.obj = object;
        }

        @Override
        public void run() {
            try {
                synchronized (obj) {
                    System.out.println("Begin notify(), ThreadName = " + Thread.currentThread().getName());
                    obj.notify();
                    Thread.sleep(5000);
                    System.out.println("End notify(), ThreadName = " + Thread.currentThread().getName());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

            次に、main()メソッドを呼び出します

 public static void main(String[] args) {
        Object object = new Object();
        NotifyThread1 notifyThread1 = new NotifyThread1(object);
        NotifyThread2 notifyThread2 = new NotifyThread2(object);
        new Thread(notifyThread1).start();
        new Thread(notifyThread2).start();
    }

          操作の結果を見てみましょう。

          実行結果によると、分析できます。notify()メソッドがロックを解放する場合、Thread-1がnotify()を呼び出した後、Thread.sleep(5000)中に他のスレッドがコードブロックを同期するために介入する必要があります。実行結果は明らかにスレッド1を待機しています。スリープが完了した後も実行が継続されるため、notify()メソッドがロックを解放できないことが証明されます。

 

この記事はこれで完成し、主にスレッド間の共有とコラボレーションについて紹介します。次の記事では、マルチスレッドについて引き続き学習します。

関連記事を読む

javaマルチスレッド学習(1)

2つを学習するJavaマルチスレッド(スレッドの開始と終了)

Javaマルチスレッド学習3(スレッド間の共有とコラボレーション)

 

 

 

 

おすすめ

転載: blog.csdn.net/zhourui_1021/article/details/89350823