Javaにおけるマルチスレッドセキュリティ、同期、デッドロック、ウェイクアップ待ちメカニズム

7664 ワード

目次
一、スレッドセキュリティ問題
二、スレッド同期
1、同期コードブロック
2、同期方法
三、デッドロック
四、ロックインタフェース
五、待機と起動メカニズム
一、スレッドセキュリティ問題
複数のスレッドが同時に実行され、これらのスレッドがこのコードを同時に実行する可能性がある場合、プログラムの実行結果は単一スレッドの実行結果と同じであり、プログラム内の変数値は予想通りであれば、スレッドは安全であり、そうでなければスレッドは安全ではありません.
次はチケット販売のケースでスレッドセキュリティの問題を理解します
//     
public class Ticket implements Runnable{
    int T = 100;    //  100  
    @Override
    public void run() {
        while (true)
        {
            try {
                Thread.sleep(10);    //    ,          
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(T > 0)
            {
                System.out.println(Thread.currentThread().getName() + T--);
            }

        }
    }
}

public static void main(String[] args)
{
    //  Runnable       
    Ticket t = new Ticket();
    //    Thread  ,  Runnable     
    Thread T1 = new Thread(t,"  1:");
    Thread T2 = new Thread(t,"  2:");
    Thread T3 = new Thread(t,"  3:");
    //    
    T1.start();
    T2.start();
    T3.start();
}
  • コードを実行すると、重複するチケットが表示されます.これは予想された結果とは異なり、マルチスレッドセキュリティの問題
  • が発生します.
  • マルチスレッドセキュリティの問題はすべてグローバル変数と静的変数によって引き起こされ、もし各スレッドの中でグローバル変数、静的変数に対して読み取り操作だけで書き込み操作がなければ、一般的には、このグローバル変数はスレッドセキュリティである.複数のスレッドが同時に書き込みを実行すると、スレッドセキュリティの問題が発生する可能性があり、この問題を解決するためにスレッド同期を採用するのが一般的である
  • .
  • Javaでプリエンプトスケジューリングを行い、プログラムがチケットクラスで実行されるとrunメソッドのwhileサイクルのifサイクルに入るとCPUを奪われずにブロックが発生する可能性があり、この3つのスレッドがこの位置にブロックされる可能性があるため、負数が印刷される場合があります
  • .
    二、スレッド同期
    Javaではスレッド同期メカニズムが提供され、スレッドセキュリティの問題を効果的に解決し、スレッド同期には以下の2つの方法があります.
  • 方法一:同期コードブロック
  • 方法2:同期方法
  • 1、同期コードブロック
    フォーマット:コードブロック宣言にsynchronizedsynchronichronized(ロックオブジェクト){スレッドセキュリティの問題が発生する可能性があるコードブロック}を追加
    注意:同期コード・ブロック内のロック・オブジェクトは任意のオブジェクトであってもよいが、複数のスレッドの場合、同じロック・オブジェクトを使用してスレッドのセキュリティを保証する
    チケット販売のケースを改善します.
    public class Ticket implements Runnable{
        int T = 10;
        //     
        Object lock = new Object();
        @Override
        public void run() {
            while (true)
            {
                //     
                synchronized (lock)
                {
                    if(T > 0)
                    {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + T--);
                    }
                }
            }
        }
    }

    上記のコードを改良し、同期コードブロック、すなわち同期ロックを追加し、スレッドが同期コードブロックに入ると、同期ロックがあるかどうかを判断し、ある場合は同期ロックを取得し、同期中に入り、コードブロックを実行し、実行が完了すると、同期コードブロックを出て、スレッドはロックを返し、ロックがないと判断した場合、同期コードブロックの外にブロックされて実行できず、待つしかなく、スレッドセキュリティの問題は解決したが、プログラムの実行速度が低下した.合計:ロックされていないスレッドは同期に入ることができず、同期中のスレッドは、同期に出ないとロックは解放されません.
    2、同期方法
    (1)一般的な方法の同期
    フォーマット:メソッド宣言にsynchronizedpublic synchronized void method(){スレッドセキュリティの問題が発生する可能性があるコード}を追加
    注意:同期メソッドのロックオブジェクトはthisです.
    チケット販売のケースをさらに改善します.
    public class Ticket implements Runnable{
        int T = 10;
        //     
        Object lock = new Object();
        @Override
        public void run() {
            while (true)
            {
                //    
                method();
            }
        }
    
        private synchronized void method()
        {
            if(T > 0)
            {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + T--);
            }
        }
    }

     
    同期メソッドはスレッドセキュリティの問題も解決します
    (2)静的同期方法
    フォーマット:メソッド宣言にstatic synchronizedpublic static synchronized void method(){スレッドの安全なコードが生成される可能性があります}を追加します.
    注意:静的同期メソッドのロックオブジェクトはクラス名です.class
    三、デッドロック
    同期ロックを使用する場合、スレッド・タスクで複数の同期(複数のロック)が発生した場合、同期に他の同期がネストされている場合、プログラムの無限待ちが起こりやすいという弊害があります.この現象をデッドロックと呼びます.
    フォーマット:synchronized(Aロック){synchronized(Bロック){}
    四、ロックインタフェース
    ロックインタフェース実装は、synchronizedメソッドと文を使用するよりも広範なロック動作を提供します.ロックインタフェースでよく使用される方法は次のとおりです.
  • void lock():ロック
  • を取得
  • void unlock():リリースロック
  • ロックインタフェースを使用して、チケット販売のケースを引き続き修正します.
    public class Ticket implements Runnable{
        int T = 10;
        //  Lock  
        Lock ck = new ReentrantLock();
        @Override
        public void run() {
            while (true)
            {
                //  lock     
                ck.lock();
                    if(T > 0)
                    {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + T--);
                    }
                //   
                ck.unlock();
            }
        }
    }

    五、待機と起動メカニズム
    待機起動メカニズムは、プロセス間の通信を容易にするための手段であり、複数のスレッドが同一のリソースを処理する際、処理の動作(スレッドのタスク)が同一でないため、各スレッドがリソースを有効に利用できるように待機起動メカニズムを採用した.待機起動メカニズムに係る方法:
  • wait():待機します.実行中のスレッドを実行資格と実行権を解放し、スレッドプール
  • に格納する.
  • notify():起動します.起動スレッドプールのwait()スレッドは、1回に1つ起動され、任意の
  • である.
  • notifyAll():すべてを呼び覚ます.スレッドプール内のすべてのwati()スレッドを
  • に起動できます.
    注意:
  • これらの方法はいずれも同期中に有効であり、使用時に所属するロックを明記しなければならない.これにより、これらの方法がどのロック上のスレッド
  • を操作するかを明確にすることができる.
  • これらの方法は使用時に所属するロックを明記し、ロックは任意のオブジェクトであるため、これらの方法はObjectクラスに定義された
  • である.
    コードの例:
    一例として、既存のPersonクラスは、名前と年齢を格納し、inPutスレッドを使用してPersonクラスに情報を入力し、outPutスレッドを使用してPersonクラスに印刷情報を取得する
    //  Person 
    public class Person {
        String name;
        int age;
        boolean flag = false;
    }
    //      inPut 
    public class inPut implements Runnable {
        private Person p;
        int count = 0;
        public inPut(Person p) {
            this.p = p;
        }
    
        public void run() {
            while (true)
            {
                synchronized (p)
                {
                    if(p.flag)
                    {
                        try {
                            p.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if(count % 2 == 0)
                    {
                        p.name = "  ";
                        p.age = 3;
                    }
                    else
                    {
                        p.name = "  ";
                        p.age = 99;
                    }
                    p.notify();
                    p.flag = true;
                }
                count++;
            }
        }
    }
    //      outPut 
    public class outPut implements Runnable {
        private Person p;
        public outPut(Person p)
        {
            this.p = p;
        }
        public void run() {
            while (true)
            {
                synchronized (p)
                {
                    if(!p.flag)
                    {
                        try {
                            p.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(p.name + ":" + p.age + " ");
                    p.notify();
                    p.flag = true;
                }
            }
        }
    }
    //       
    public static void main(String[] args)
    {
        Person P = new Person();
    
        inPut in = new inPut(P);
        outPut out = new outPut(P);
    
        Thread T1 = new Thread(in);
        Thread T2 = new Thread(out);
    
        T1.start();
        T2.start();
    }

    分析:
  • 入力inPutクラス:入力が完了すると、出力結果の印刷が終了するまで待機しなければならず、付与後、wait()メソッドを実行して、起動されるまで待機し、起動後に変数を再付与し、付与後に出力スレッドnotify()を起動し、自分でwait()
  • を起動する.
  • 出力outPutクラス:出力が完了した後、入力の再割り当てを待ってから次の出力を行う必要があります.出力が待機する前に、入力のnotify()を呼び出し、
  • が起動するまで自分でwait()を永遠に待たなければなりません.