JAva学習ノート(12)--スレッド同期とデッドロック


スレッドの同期***********
同期の問題:各スレッド・オブジェクトが共有リソースを順番に占有することによる問題
まずコードを見てみましょう.
class MyThread implements Runnable{
    private int ticket=10;
    @Override
    public void run() {
        while (this.ticket>0){
            try{
                //      
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+
",  "+this.ticket--+"  ");
        }
    }
}
public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt=new MyThread();
        new Thread(mt,"  A").start();
        new Thread(mt,"  B").start();
        new Thread(mt,"  C").start();
    }
}
                  ,          ,          :
......
  B,  1  
  C,  0  
  A,  -1  

上記の現象を非同期操作と呼ぶことができ、その唯一の利点は処理速度が速い(複数のスレッドが同時に実行される)ことである.
1同期処理
1.1 synchronizeキーワード(内蔵ロック、JDK 1.0をキーワードとして提供する同期手段)を使用して同期問題オブジェクトロックを処理するロックの対象を明確にしなければならない
synchronize処理同期には、同期コードブロック、同期方法の2つのモードがあります.
同期コードブロック:(使用を推奨、ロック粒度が細い)同期コードブロックを使用するには、ロックされたオブジェクトを設定する必要があります.一般的には、現在のオブジェクトをロックできます.
同じ時刻にコードブロックに入るスレッドは1つしかありません.メソッド内はマルチスレッドです.
synchronized(this){
//コードブロックの同期が必要
}
class MyThread implements Runnable{
    private int ticket=1000;
    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            synchronized (this){
                if(this.ticket>0){
                    System.out.println(Thread.currentThread().getName()+
",  "+this.ticket--+"  ");
                }
            }
        }
    }
}
public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt=new MyThread();
        new Thread(mt,"  A").start();
        new Thread(mt,"  B").start();
        new Thread(mt,"  C").start();
    }
}
            :
......
  B,  3  
  B,  2  
  B,  1  

同期メソッド:synchronizeキーワードをメソッドに追加し、このメソッドにアクセスできるスレッドが1つしかないことを示します.隠しロックオブジェクト、this
このメソッドにアクセスできるのは、同じ時点で1つのスレッドのみです.
例:
class MyThread implements Runnable{
    private int ticket=1000;
    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            if(this.ticket>0){
                this.sala();
            }
        }
    }
    public synchronized void sala(){
        if(this.ticket>0){
            try {
                //      
                Thread.sleep(20);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+
",  "+this.ticket--+"  ");
        }
    }
}
public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt=new MyThread();
        new Thread(mt,"  A").start();
        new Thread(mt,"  B").start();
        new Thread(mt,"  C").start();
    }
}
            :
......
  C,  3  
  A,  2  
  A,  1  

同期の欠点:データの整合性(スレッドセキュリティ動作)は保証されますが、StringBuilderとStringBufferの実行速度は遅くなります.
  • StringBuffer  JDK1.0、同期処理を採用し、スレッドが安全で、効率が低い
  • StringBuilder   JDK1.5、非同期処理を採用し、スレッドが安全ではなく、効率が高い
  • 1.2 synchronizedに関する追加の説明
    ①実はsynchronized(this)
    static以外のsynchronizedメソッドでは、複数のスレッドが同じオブジェクトの同期コードブロックを同時に実行することを防止できます.
    synchronizedはオブジェクトロック(デフォルトでは1つのオブジェクトしかロックできません)と呼ばれ、オブジェクト自体であるthisをロックします.
    例:synchronizedの追加説明を観察する
    class Sync{
        public synchronized void test(){    //         
            System.out.println("test    ,    "+Thread.currentThread().getName());
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("test    ,     "+Thread.currentThread().getName());
        }
    }
    class MyThread extends Thread{
      //  static      Sync  ,            
        //static Sync sync=new Sync();
        @Override
        public void run() {
            Sync sync=new Sync();
            sync.test();
        }
    }
    public class Test2 {
        public static void main(String[] args) {
            for(int i=0;i<3;i++){
                Thread thread=new MyThread();
                thread.start();
            }
        }
    }
        ,        ,    :
    test    ,    Thread-0
    test    ,    Thread-1
    test    ,    Thread-2
    test    ,     Thread-0
    test    ,     Thread-1
    test    ,     Thread-2

    このコードをロックするには、次の3つの考え方があります.
    例:同じオブジェクトをロックする
    class Sync{
        public synchronized void test(){
            System.out.println("test    ,    "+Thread.currentThread().getName());
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("test    ,     "+Thread.currentThread().getName());
        }
    }
    class MyThread extends Thread{
        private Sync sync;
        public MyThread(Sync sync){
            this.sync=sync;
        }
        @Override
        public void run() {
            this.sync.test();
        }
    }
    public class Test2 {
        public static void main(String[] args) {
            //     Sync  
            Sync sync=new Sync();
            for(int i=0;i<3;i++){
                Thread thread=new MyThread(sync);
                thread.start();
            }
        }
    }

    オブジェクト・ロックとグローバル・ロック:
    synchronizedデフォルトオブジェクトロック、コードブロックではなく現在のオブジェクトロック
    グローバルロックは、オブジェクトに関係なく、真のコードセグメントです.
    グローバル・ロックを実現する2つの方法:
    1.同期コードセグメントでClassオブジェクトをロックする
    2.static synchronizedメソッドの使用
    例:グローバルロックを使用して、thisオブジェクトではなくクラスをロックします.
    class Sync{
        public void test(){
            //     ,        ,     
            synchronized (Sync.class){
                System.out.println("test    ,    "+Thread.currentThread().getName());
                try{
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println("test    ,     "+Thread.currentThread().getName());
            }
        }
    }
    class MyThread extends Thread{
        @Override
        public void run() {
            Sync sync = new Sync() ;
            sync.test();
        }
    }
    public class Test2 {
        public static void main(String[] args) {
            for(int i=0;i<3;i++){
                Thread thread=new MyThread();
                thread.start();
            }
        }
    }

    static synchronizedメソッド、staticメソッドは直接クラス名にメソッド名を付けて呼び出すことができ、メソッドではthisを使用できないため、このロックはthisではなくクラスのClassオブジェクトであるため、static synchronizedメソッドもグローバルロックに相当し、コードセグメントをロックしたことに相当する.
    例:static synchronizedメソッドの使用
    class Sync{
        //          ,     
        public static  synchronized void test(){    //         
            System.out.println("test    ,    "+Thread.currentThread().getName());
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("test    ,     "+Thread.currentThread().getName());
        }
    }
    class MyThread extends Thread{
        @Override
        public void run() {
            Sync sync=new Sync();
            sync.test();
        }
    }
    public class Test2 {
        public static void main(String[] args) {
            for(int i=0;i<3;i++){
                Thread thread=new MyThread();
                thread.start();
            }
        }
    }

    例:1段のコード(****重要****)の典型的な面接問題を見る
    class MyThread implements Runnable{
        private ThreadTest test;
            public MyThread(ThreadTest test){
            super();
            this.test=test;
        }
        @Override
        public void run() {
            if(Thread.currentThread().getName().equals("Thread-1")){
                this.test.testA();
            }else if(Thread.currentThread().getName().equals("Thread-2")){
                this.test.testB();
            }
        }
    }
    class ThreadTest{
        public synchronized void testA(){
            System.out.println(Thread.currentThread().getName()+"tsetA  ");
            while(true){}
        }
        public synchronized void testB(){
            System.out.println(Thread.currentThread().getName()+"tsetB  ");
        }
    }
    public class Test2 {
        public static void main(String[] args) throws InterruptedException {
            ThreadTest test=new ThreadTest();
            MyThread mythread=new MyThread(test);
            Thread thread1=new Thread(mythread);
            Thread thread2=new Thread(mythread);
            thread1.start();
            Thread.sleep(1000);
            thread2.start();
        }
    }

    スレッド2はtestBに入れますか?
    ロックされているのは現在のオブジェクトで、2つの同期メソッドは同じオブジェクトでロックされています.
    同じオブジェクトで、1つのクラスに2つの同期メソッドがある場合、1つのスレッドがすでに1つの同期メソッドに入っている場合、別のスレッドは絶対に別の同期メソッドに入ることはできません.
     
    同期メソッドの一般的なメソッドがある場合、別のスレッドは一般的なメソッドに進むことができます.
    1.3オブジェクトロックメカニズム---JDK 6以前のsynchronized(ヘビー級ロック)
    同期コードブロック:同期コードブロックを実行した後、まずmonitorrenter命令を実行し、終了時にmonitorexit命令を実行します.
    内蔵ロック(synchronized)を使用して同期します.重要なのは、指定したロックオブジェクトmonitorオブジェクトを取得することです.スレッドがmonitorを取得してから下へ実行し続けることができます.そうしないと、待つしかありません.この取得プロセスは、同じ時点でオブジェクトモニタを取得できるスレッドが1つしかないという反発的である.
     
    通常、1つのmonitorenterコマンドには、いくつかのmonitorexitコマンドが含まれます.その理由は、JVMが正常な実行経路および異常な実行経路で正しくロックを解除し、いかなる場合でもロックを解除できるようにする必要があるからです.
    同期メソッド:synchronizedタグメソッドがすべて使用されている場合、コンパイル後のバイトコード内のメソッドのアクセスタグにACC_が1つ増えます.SYNCHRONIZED.このタグは、このメソッドに入ると、JVMはモニタ操作を行う必要があり、このメソッドを終了すると、正常に戻るかどうかにかかわらず、JVMはモニタrexit操作を行う必要があることを示しています.
    モニタrenterを実行すると、ターゲットロックオブジェクトのモニタカウンタが0の場合、このオブジェクトが他のオブジェクトに所有されていないことを示します.このときJVMは、そのロック対象の保有スレッドを現在のスレッドに設定し、カウンタ+1を持つ.ターゲットロック対象カウンタが0でない場合は、ロック対象の保持スレッドが現在のスレッドであるかどうかを判断し、カウンタ+1(ロックの再入性)を再度行う場合は、ロック対象の保持スレッドが現在のスレッドでない場合は、保持スレッドがロックを解除するまで待機する必要があります.
     
    monitorexitを実行すると、JVMはロックオブジェクトのカウンタ-1を、カウンタが0に減少すると、ロックが解放されたことを示します.
     
    JDK1.5提供するロックロック(了解可)(対象ロックでも構いません)
    例:ReentrantLockで同期処理を行い、ロックが必要な場合はロック、finallyでunLock
    class MyThread implements Runnable{
        private int ticket=100;
        private Lock ticketLock=new ReentrantLock();
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                try{
                    ticketLock.lock();
                    if(this.ticket>0) {
                    System.out.println(Thread.currentThread().getName() +
    "   :" + this.ticket-- + " ");
                    }
                }finally{
                    //     
                    ticketLock.unlock();
                }
            }
        }
    }
    public class Test2 {
        public static void main(String[] args) throws InterruptedException {
            MyThread mt=new MyThread();
            Thread thread1=new Thread(mt,"  1");
            Thread thread2=new Thread(mt,"  2");
            Thread thread3=new Thread(mt,"  3");
            thread1.setPriority(Thread.MIN_PRIORITY);
            thread2.setPriority(Thread.MAX_PRIORITY);
            thread3.setPriority(Thread.MAX_PRIORITY);
            thread1.start();
            thread2.start();
            thread3.start();
        }
    }