(三)synchronizedを用いてスレッド間の通信を実現する

9231 ワード

スレッドとスレッドの間で実行されるタスクは異なりますが、スレッドとスレッドの間で動作するデータは同じです.
1、構築例
マルチスレッドが学生の情報を同時に読み取り、出力することを実現する1.学生を格納するためのクラスを作成する.2.入力タスクを保存するクラスを作成します.3.出力タスクを保存するクラスを作成します.4.パラメータ伝達方式を用いて、2つのスレッドに同じパラメータを使用させ、スレッド間の通信を実現する.

//     ,     
class Student {
    String name;
    String sex;
}

//       
class Input implements Runnable {
    /*
     *         
     *   2       
     *        
     */
    private Student stu;
    public Input(Student stu) {
        this.stu = stu;
    }
    public void run() {
         //          ,  if    
        int i = 1;
        //              ,  while  
        while (true) {
                if (i == 1) {
                    stu.name = "Tom";
                    stu.sex = " ";
                } else {
                    stu.name = "Monica";
                    stu.sex = " ";
                }
            //   i   , i 1 0      
            i = (i + 1) % 2;
        }
    }
}

//       
class Output implements Runnable {
    private Student stu;
    public Output(Student stu) {
        this.stu = stu;
    }
    public void run() {
        //   while        
        while (true) {
                System.out.println(stu.sex + " :" + stu.name);  
        }
    }
}

public class Demo1 {

    public static void main(String[] args) {
        //       
        Student stu = new Student();
        //       ,    stu
        Input in = new Input(stu);
        //       ,    stu
        Output out = new Output(stu);
        //       
        Thread t1 = new Thread(in);
        //       
        Thread t2 = new Thread(out);
        t1.start();
        t2.start();

    }

}

結果は次のとおりです.
「トム」、「男」、「Monica」、「女」の情報が入っていたはずなのに、エラーが発生しました.このようなエラーの原因は、入力スレッドが「Tom」、「男」を記憶した後、下方向に動作し、iの値を0に変更してループを継続し、i=0のときに「Monica」を記憶した直後にCPUが出力スレッドに奪われたが、入力スレッドが「女」を記憶していないため、出力スレッドが「Monica」、「男」を出力したためである.
2、共有データのセキュリティ問題
学生の情報はこの例のコードの中で共有データに属して、入力スレッドと出力スレッドが共同で操作して、どのようにデータの安全を実現して、前の誤りを避けて、synchronizedコードブロックを使ってタスクコードを囲んで、以下のように修正することができます:
//       
class Input implements Runnable {
    private Student stu;
    public Input(Student stu) {
        this.stu = stu;
    }
    public void run() {
        int i = 1;
        while (true) {
            //  synchronized      
            synchronized (stu) {
                if (i == 1) {
                    stu.name = "Tom";
                    stu.sex = " ";
                } else {
                    stu.name = "Monica";
                    stu.sex = " ";
                }
            }
            i = (i + 1) % 2;
        }
    }
}

//       
class Output implements Runnable {
    private Student stu;
    public Output(Student stu) {
        this.stu = stu;
    }
    public void run() {
        while (true) {
            //  synchronized      
            synchronized (stu) {
                System.out.println(stu.sex + " :" + stu.name);
            }
        }
    }
}

結果は次のとおりです.
synchronizedを使用して同期ロックを実現する2つの条件:1つ目は2つ以上のスレッドが存在し、2つ目は複数のスレッドが同じロックを使用することです.ここではまず入力と出力の2つのスレッドがあり,次に,スレッドが使用するロックはいずれもstuオブジェクトであり,ロックの統一を実現し,効果を保証した.
3、スレッド待ちとスレッド起動の設定
修正の結果、エラーは解消されたものの、同じ学生情報を長時間出力し続けることになります.この場合、出力スレッドがCPUを占有して出力されるため、パフォーマンスの無駄になります.これを避けるために、1つのデータを格納したときに、もう1つのデータを出力することを実現します.待機起動メカニズムを使用することができます.コードは以下の通りです.

//     ,     
class Student {
    String name;
    String sex;
    /*
     *                 
     *              flag  
     *      false
     */
    boolean flag;
}

//       
class Input implements Runnable {
    private Student stu;
    public Input(Student stu) {
        this.stu = stu;
    }
    public void run() {
        int i = 1;
        while (true) {
            synchronized (stu) {
                /*
                 *             
                 *   flag true
                 *     
                 *         
                 *         
                 *   flag false
                 *     wait()  
                 *       
                 *        else
                 */
                if(stu.flag){
                    try {
                        /*
                         * wait()        
                         *    run()    
                         *     try-catch   
                         *     
                         * wait()            
                         *    ,           
                         *       stu          
                         *            
                         */
                        stu.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (i == 1) {
                    stu.name = "Tom";
                    stu.sex = " ";
                } else {
                    stu.name = "Monica";
                    stu.sex = " ";
                }
                //           flag  
                stu.flag = true;
                /*
                 *         
                 *         
                 *  wait()    
                 * notify()          
                 *        
                 *        stu      
                 */
                stu.notify();
            }
            i = (i + 1) % 2;
        }
    }
}

//       
class Output implements Runnable {
    private Student stu;
    public Output(Student stu) {
        this.stu = stu;
    }
    public void run() {
        while (true) {
            synchronized (stu) {
                /*
                 *           
                 *    flag    true
                 *        false
                 *     
                 *      ture
                 *       
                 */
                if(!stu.flag){
                    try {
                        stu.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(stu.sex + " :" + stu.name);
                //           flag  
                stu.flag = false;
                //         ,        
                stu.notify();
            }
        }
    }
}

public class Demo1 {

    public static void main(String[] args) {
        //       
        Student stu = new Student();
        //       
        Input in = new Input(stu);
        //       
        Output out = new Output(stu);
        //       
        Thread t1 = new Thread(in);
        //       
        Thread t2 = new Thread(out);
        t1.start();
        t2.start();
    
    }

}


結果は次のとおりです.
ウェイクアップ待ちメカニズムを使用すると、1つのデータを格納してから1つのデータを出力する効果を実現し、ウェイクアップ待ちメカニズムを簡単にまとめます.
wait()メソッド、notify()メソッド、notifyAll()メソッドは同期コードに使用する必要があります.同期コードにのみロックがありますが、この3つのメソッドはロックを使用して呼び出す必要があります.どのスレッドがロックを持っているか、どのスレッドが待機または起動状態になります.
  • **wait()メソッド:**スレッドを待機状態にし、実際にスレッドをスレッドプールに入れます.
  • notify()メソッド:スレッドプール内の任意のスレッドを起動し、特定のロックを持つスレッドであるが、複数のスレッドが同じロックを持つことができるため、実際には任意の特定のロックを持つスレッドを起動する.
  • notifyAll()メソッド:すべてのスレッドを起動します.

  • この3つのメソッドをObjectクラスに定義するのは、いずれのオブジェクトもロックとして使用できるため、いずれのオブジェクトもこの3つのメソッドを呼び出すことができるため、すべてのクラスの親、すなわちObjectクラスでのみ定義できます.
    4、最適化完備サンプルコード
    
    class Student {
        private String name;
        private String sex;
        private boolean flag;
    
        //         
        public synchronized void set(String name, String sex) {
            if (flag) {
                try {
                    wait();//       this,    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name;
            this.sex = sex;
            flag = true;
            notify();
        }
    
        //         
        public synchronized void out() {
            if (!flag) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(sex + " :" + name);
            flag = false;
            notify();
        }
    }
    
    //       
    class Input implements Runnable {
        private Student stu;
    
        public Input(Student stu) {
            this.stu = stu;
        }
    
        public void run() {
            int i = 1;
            while (true) {
                if (i == 1) {
                    stu.set("Tom", " ");
                } else {
                    stu.set("Monica", " ");
                }
                i = (i + 1) % 2;
            }
        }
    }
    
    //       
    class Output implements Runnable {
        private Student stu;
    
        public Output(Student stu) {
            this.stu = stu;
        }
    
        public void run() {
            while (true) {
                stu.out();
            }
        }
    }
    
    public class Demo1 {
    
        public static void main(String[] args) {
    
            Student stu = new Student();
    
            Input in = new Input(stu);
            Output out = new Output(stu);
    
            Thread t1 = new Thread(in);
            Thread t2 = new Thread(out);
    
            t1.start();
            t2.start();
    
        }
    
    }
    
    

    注意:JKDバージョンの更新に伴い、1.5バージョン以降synchronizedよりも強力な同期ロックを実現する方法が現れ、詳細はロックインタフェースとConditionインタフェースを使用して生産者と消費者を実現することを参照してください.
        :    ,    ,               ,    !             ↓↓↓