Javaマルチスレッド2-オブジェクトと変数の同時アクセス(概念理解)

16504 ワード

Javaマルチスレッド2-オブジェクトと変数の同時アクセス(概念理解)
 java         ,             "        "——             。

上記の非スレッドセキュリティの問題を解決する方法:
  • 非同期問題が変数によって引き起こされる場合、その変数は必ずクラスのグローバル変数であり、この場合、スレッドによって呼び出されたメソッド内のローカル変数に変更すれば、データの非同期の問題を解決することができる.
  • .スレッドによって呼び出されたメソッドにsynchronizedキーワードを追加します.これはスレッドの非同期問題を解決する最も一般的な方法です.synchronizedキーワードがある方法は同期方法です.
    注意事項:
    synchronizedキーワードは、現在のスレッドクラスの複数のオブジェクトが同時にオンになっている場合、各オブジェクトにそれぞれのオブジェクトロックがあり、それぞれのrunメソッドを実行できるため、Threadクラスのサブクラスに継承されたrun()メソッドに追加することはできません.これで同期の役割は果たせません.
    run()メソッドにsynchronizedキーワードを付けなければならない場合は、Threadクラスを継承せずにRunnable()インタフェースを実装するしかありません.

  • ダーティリード:インスタンス変数を読み込む前に、この値は別のスレッドで変更された可能性があります.
    synchronizedメソッドとロックオブジェクト
  • AスレッドがanyobjectオブジェクトをsynchronizedキーワードのXメソッドに追加すると、AスレッドはXメソッドロックを取得し、より正確にはオブジェクトのロックを取得するので、他のスレッドはAスレッドの実行が完了するまでXメソッドを呼び出すことはできないが、Bスレッドは他の非synchronized同期メソッドを任意に呼び出すことができる.
  • AスレッドがanyobjectオブジェクトをsynchronizedキーワードのXメソッドに追加すると、AスレッドはXメソッドが存在するオブジェクトのロックを取得するので、他のスレッドはAスレッドが実行されるまでXメソッドを呼び出すことができないが、Bスレッドがsynchronizedキーワードを宣言した非Xメソッドを呼び出す場合、AスレッドがXメソッドを実行するのを待たなければならない.つまり、オブジェクトロックを解除してから呼び出すことができます.
  • キーワードsynchronizedは、synchronizedを使用すると、スレッドがオブジェクトロックを取得した後、再びオブジェクトロックを要求すると、再びこのオブジェクトロックを取得することができるロック再入機能を有する.これは、1つのsynchronizedメソッドの内部で本クラスの他のsynchronizedメソッドを呼び出すと、永遠にロックが得られることを証明します.(再読み込み可能ロック:内部ロックを再取得できることを意味します)親子の継承関係がある場合、子クラスは親を再読み込み可能ロックで呼び出すことができる同期メソッドです.次に、
  • の例を示します.
    /**
     *     
     */
    public class Main {
        public int i=10;
        synchronized public void operateIMainMethod(){
            try {
                i--;
                System.out.println("main print i="+i);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public  static void main(String []args){
            Mythread t = new Mythread();
            t.start();
        }
    }
    
    
    
    /**
     *   
     */
    class Sub extends Main{
        synchronized public void operateISubMethod(){
            try {
                while(i>0){
                    i--;
                    System.out.println("sub print i="+i);
                    Thread.sleep(100);
                    this.operateIMainMethod();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     *    
     */
    class Mythread extends Thread{
        @Override
        public void run() {
            super.run();
            Sub sub=new Sub();
            sub.operateISubMethod();
        }
    }

    実行結果:sub print i=9 main print i=8 sub print i=7 main print i=6 sub print i=5 main print i=4 sub print i=3 main print i=2 sub print i=1 main print i=0
    スレッドが実行するコードに異常が発生すると、その持つロックは自動的に解放されます.同期と非同期の違い:*複数のスレッドが1つのコードセグメントに同時にアクセスする場合、このコードセグメントの一部のコードはsynchronizedによって修飾された同期コードブロックに含まれ、もう一部は同期コードブロックの外にある場合、同期コードブロックの中のコードを同期と呼び、同期コードブロック以外のコードを実行する場合、非同期と呼ばれています.*同期、同じ時間に1つのスレッドのみが*非同期にアクセスでき、同じ時間に複数のスレッドが同時にアクセスできる(論理的には物理的に必ずしも同時ではない)
    同期メソッドと同期コードブロックの間で注意すべき点:
    **           synchronized    ,         ,      ,        **
    

    *1同期コードブロック内のオブジェクトがthisオブジェクトである場合:*synchronizedで修飾された他のメソッドまたはsynchronized(this)同期コードブロックはブロックされ、同期メソッドまたは同期コードブロック内のコード、すなわち同期コードブロックが同期メソッドと同期されるのは、同じ時間に1つのスレッドのみで実行できます.
  • 2.同期コードブロックで使用するオブジェクトがthis以外のオブジェクトである場合:
  • 同期コードブロックの実行と同期方法の実行との間は非同期である.

  • synchronized(thisオブジェクトx以外)で使用される3つの結論について:
  • 複数のスレッドがsynchronized(x){}同期コードブロックを同時に実行すると同期効果を示す.
  • 他のスレッドがxオブジェクトのsynchronized同期方法を実行すると同期効果を示す.
  • 他のスレッドがxオブジェクトメソッドのsynchronized(this)コードブロックを実行する場合も同期効果が現れる.

  • 静的同期synchronizedメソッドとsynchronized(class)コードブロックについて
    synchronized      static         ;synchronized  static     Class   。            , Class            。
    

    *オブジェクトロック:synchronized(this)またはsynchronized(A a)*Classロック:synchronized(A.class)
    データ型Stringの定数プールがマルチスレッドに与える影響
    まず例を見てみましょう.
    public class StringTest{
    
        public void print(String s) {
            synchronized (s) {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + "--------");
                }
            }
        }
    
    
        public  static void main(String []args){
            StringTest st=new StringTest();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    st.print("a");
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    st.print("a");
                }
            }).start();
        }
    }

    実行結果は、Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0ead-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--Thread-0--実行結果から分かるように、1つのスレッドしか実行されていません.一方、別のスレッドは実行されていない.これはString定数プールの原因であり、2つのスレッドのrunメソッドでprint()メソッドを呼び出すときに入力されるパラメータはすべて「a」である.2つのスレッドは同じロックオブジェクトを必要とし、Thread-0はロックオブジェクトを持っており、デッドサイクルに入り、スレッドThread-1が実行されないように導く.StringパラメータをObjectオブジェクトに変更すると、次のような問題を解決できます.
    public class StringTest{
    
        public void print(Object object) {
            synchronized (object) {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + "--------");
                }
            }
        }
    
    
        public  static void main(String []args){
            StringTest st=new StringTest();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    st.print(new Object());
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    st.print(new Object());
                }
            }).start();
        }
    }

    実行結果は、Thread-0--Thread-0--Thread-0--Thread-0--Thread-1--Thread-1--Thread-1--Thread-1--Thread-1--Thread-1--Thread-1--Thread-1--Thread-1--
    デッドロック:
        :                   ,                         ,      ,          。                   ,                  。
    

    デッドロックの発生:*synchronizedネストされたコード構造:
    synchronized(lock1){
        ...
        synchronized(lock2){
            ...
        }
        ...
    }

    キーワードvolatile
       volatile                 。                   ,                   。
    

    例を見てみましょう
    public class VolatileTest extends Thread{
    
        private boolean isRunning = true;
    
        public boolean isRunning() {
            return isRunning;
        }
    
        public void setRunning(boolean running) {
            isRunning = running;
        }
    
        @Override
        public void run() {
            super.run();
            System.out.println("  run ");
            while(isRunning==true){
    
            }
            System.out.println("      ");
        }
    
        public  static void main(String []args){
    
            try {
                VolatileTest vt=new VolatileTest();
                vt.start();
                Thread.sleep(1000);
                vt.setRunning(false);
                System.out.println("     false");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    実行結果はrunに入りfalseに割り当てられたことからプログラムがデッドサイクルに入ったと断定することができ,(理由:VolatileTest.javaスレッドを開始すると、変数private boolean isRunning=trueとなり、共通スタックおよびスレッドのプライベートスタックに存在する.JVMが-serverモードに設定されている場合、スレッドの実行効率のためにスレッドはプライベートスタックで値を取っている.コードvt.setRunning(false);実行されますが、更新されたのは共通スタック内のデータで、スレッドのプライベートスタックデータはそれとともに変化せず、最終的にデッドサイクルになりました.次に、Volatileキーワードによってこの問題を解決します(Volatileキーワードはスレッドが上記のコードのisRunningにアクセスするときに、共通スタックから値を強制的に取得します):
    public class VolatileTest extends Thread{
    
        volatile private boolean isRunning = true;
    
        public boolean isRunning() {
            return isRunning;
        }
    
        public void setRunning(boolean running) {
            isRunning = running;
        }
    
        @Override
        public void run() {
            super.run();
            System.out.println("  run ");
            while(isRunning==true){
    
            }
            System.out.println("      ");
        }
    
        public  static void main(String []args){
    
            try {
                VolatileTest vt=new VolatileTest();
                vt.start();
                Thread.sleep(1000);
                vt.setRunning(false);
                System.out.println("     false");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    実行結果:runに入ってfalseスレッドに割り当てられて停止しました.実行結果によると、以前の問題は解決されました.ここではsynchronizedもスレッドワークメモリのプライベート変数と共通メモリの変数を同期させる機能を持っています.前のエラーコードを変更します.運転の効果を見てみましょう.
    public class VolatileTest extends Thread{
    
        private boolean isRunning = true;
    
        public boolean isRunning() {
    
            return isRunning;
        }
    
        public void setRunning(boolean running) {
            isRunning = running;
        }
    
        @Override
        public void run() {
            super.run();
            System.out.println("  run ");
            String anything=new String();
            while(isRunning==true){
    
                synchronized (anything){
    
                }
    
            }
            System.out.println("      ");
        }
    
        public  static void main(String []args){
    
            try {
                VolatileTest vt=new VolatileTest();
                vt.start();
                Thread.sleep(1000);
                vt.setRunning(false);
                System.out.println("     false");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    実行結果:runに入ってfalseスレッドに割り当てられた値が停止しました.結果からsynchronizedは確かにプライベート変数と共通変数の同期を果たしました.