Javaマルチスレッドベース(5)スレッド同期

16927 ワード

Javaマルチスレッドベース(5)スレッド同期
複数のスレッドを使用して同じリソースにアクセスし、複数のスレッドでリソースに書き込みがある場合、スレッドセキュリティの問題が発生しやすくなります.上記のマルチスレッドが1つのリソースに同時にアクセスするセキュリティの問題を解決するには、Javaで同期メカニズム(synchronized)が提供されています.
一、スレッド同期
スレッドAが操作に入ると、スレッドBとスレッドCは外で待つしかなく、スレッドAの操作が終わると、スレッドBとスレッドCはコードに入って実行する機会があります.つまり、あるスレッドが共有リソースを修正するとき、他のスレッドはそのリソースを修正することができず、修正が完了するのを待って、データが同期した後、CPUリソースを奪い取ることができ、対応する操作を完了し、データの同期性を保証し、スレッドの不安全な現象を解決した.
Javaは,各スレッドが原子操作を正常に実行できることを保証するために,スレッド同期機構を導入した.スレッド同期メカニズムは主にsynchronizedキーワードで、オブジェクトにロックをかけるメカニズムでスレッドの同期を保証します.
二、synchronized
(一)、synchronizedの原理
Javaでは、各オブジェクトに同期ロックが1つしかありません.これは、同期ロックがオブジェクトに依存して存在することを意味する.あるオブジェクトのsynchronizedメソッドを呼び出すと、そのオブジェクトの同期ロックが取得されます.たとえばsynchronized(obj)は、「objというオブジェクト」の同期ロックを取得する.同期ロックへの異なるスレッドのアクセスは反発します.つまり、ある時点で、オブジェクトの同期ロックは1つのスレッドでしか取得できません!同期ロックにより、マルチスレッドでオブジェクト/メソッドへの反発アクセスを実現できます.たとえば、2つのスレッドAとスレッドBが「オブジェクトobjの同期ロック」にアクセスするようになりました.ある時点で、スレッドAは「objの同期ロック」を取得し、いくつかの動作を実行していると仮定する.このとき、スレッドBは「objの同期ロック」を取得しようとする.スレッドBは失敗し、スレッドAが「オブジェクトの同期ロック」を解放した後、スレッドBが「objの同期ロック」を取得してから実行できるまで待たなければならない.
(二)、synchronizedの基本規則
synchronizedの基本ルールを以下の3つにまとめ,それらを例によって説明する.1つ目:1つのスレッドがオブジェクトのsynchronizedメソッドまたはsynchronizedコードブロックにアクセスすると、他のスレッドのオブジェクトのsynchronizedメソッドまたはsynchronizedコードブロックへのアクセスがブロックされます.2つ目:スレッドがオブジェクトのsynchronizedメソッドまたはsynchronizedコードブロックにアクセスすると、他のスレッドはオブジェクトの非同期コードブロックにアクセスできます.3つ目:1つのスレッドがオブジェクトのsynchronizedメソッドまたはsynchronizedコードブロックにアクセスすると、他のスレッドのオブジェクトの他のsynchronizedメソッドまたはsynchronizedコードブロックへのアクセスがブロックされます.
1、第一条規則
 1 public class Demo01 {
 2     public static void main(String[] args) {
 3         MyRunnable run = new MyRunnable();
 4         Thread t1 = new Thread(run);
 5         Thread t2 = new Thread(run);
 6         t1.start();
 7         t2.start();
 8     }
 9 }
10 class MyRunnable implements Runnable{
11     @Override
12     public void run() {
13         synchronized (this) {
14             try {
15                 for (int i = 0; i < 5; i++) {
16                     Thread.sleep(1000);//   1 
17                     System.out.println(Thread.currentThread().getName() + "---" + i);
18                 }
19             }catch(Exception e) {
20                 e.printStackTrace();
21             }
22         }
23     }
24 }
//     :
Thread-0---0
Thread-0---1
Thread-0---2
Thread-0---3
Thread-0---4
Thread-1---0
Thread-1---1
Thread-1---2
Thread-1---3
Thread-1---4

説明:run()メソッドにはsynchronized(this)コードブロックが存在し、t 1およびt 2はいずれもrunというRunnableオブジェクトに基づいて作成されたスレッドである.これはsynchronized(this)のthisを「runというRunnableオブジェクト」と見なすことができることを意味する.したがって、スレッドt 1とt 2は「runオブジェクトの同期ロック」を共有する.したがって、1つのスレッドが実行されると、別のスレッドは、実行スレッドがrunの同期ロックを解放するのを待ってから実行する必要があります.
2、第二条規則
public class Demo01 {
    public static void main(String[] args) {
        Demo01 demo = new Demo01();
        
        new Thread(() -> {
            demo.synCount();
        }).start();// Lambda      
        
        new Thread(() -> {
            demo.notSynCount();
        }).start();// Lambda      
    }
    //         
    public void synCount() {
        synchronized (this) {
            try {
                for(int i = 0;i <5;i++) {
                    Thread.sleep(1000);//   1 
                    System.out.println(Thread.currentThread().getName() + " syn count method " + i);
                }
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    //       
    public void  notSynCount() {
        try {
            for(int i = 0;i <5;i++) {
                Thread.sleep(1000);//   1 
                System.out.println(Thread.currentThread().getName() + " not syn count method " + i);
            }
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
}
//     
Thread-0 syn count method 0
Thread-1 not syn count method 0
Thread-1 not syn count method 1
Thread-0 syn count method 1
Thread-0 syn count method 2
Thread-1 not syn count method 2
Thread-0 syn count method 3
Thread-1 not syn count method 3
Thread-0 syn count method 4
Thread-1 not syn count method 4

説明:メインスレッドにLambda方式で2つのサブスレッドThread-0とThread-1が新規作成されました.Thread-0は、同期ブロックを含むdemoオブジェクトのsynCount()メソッドを呼び出す.一方、THread-1では、同期メソッドではないdemoオブジェクトのnotSynCount()メソッドが呼び出されます.Thread-0実行時、synchronized(this)を呼び出して「demoの同期ロック」を取得するが、しかし、Thread−1は「demo」同期ロックを使用していないため、t 2のブロックは生じなかった.
3、第三条規則
public class Demo01 {
    public static void main(String[] args) {
        Demo01 demo = new Demo01();
        
        new Thread(() -> {
            demo.synCount();
        }).start();// Lambda      
        
        new Thread(() -> {
            demo.notSynCount();
        }).start();// Lambda      
    }
    //         
    public void synCount() {
        synchronized (this) {
            try {
                for(int i = 0;i <5;i++) {
                    Thread.sleep(1000);//   1 
                    System.out.println(Thread.currentThread().getName() + " syn count method " + i);
                }
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    //         
    public void  notSynCount() {
        synchronized(this) {
            try {
                for(int i = 0;i <5;i++) {
                    Thread.sleep(1000);//   1 
                    System.out.println(Thread.currentThread().getName() + " not syn count method " + i);
                }
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
}
//     
Thread-0 syn count method 0
Thread-0 syn count method 1
Thread-0 syn count method 2
Thread-0 syn count method 3
Thread-0 syn count method 4
Thread-1 not syn count method 0
Thread-1 not syn count method 1
Thread-1 not syn count method 2
Thread-1 not syn count method 3
Thread-1 not syn count method 4

説明:メインスレッドに2つのサブスレッドThread-0とThread-1が新規に作成されました.両方の実行時にsynchronized(this)が呼び出されます.このthisはdemoオブジェクトであり、両方はdemoオブジェクトを共有します.したがって、Thread-0が実行されると、Thread-1がブロックされ、Thread-0が実行されるのを待って「demoオブジェクトの同期ロック」が解放され、Thread-1が実行されます.
三、スレッド同期方式
(一)、同期コードブロック
同期コードブロックはsynchronizedキーワードによってプログラムを修飾するコードブロックであり、synchronizedキーワードは方法のあるブロックで使用でき、このブロックのリソースに対してのみ反発アクセスを行うことを示す.フォーマットの例は次のとおりです.
public void count{
    synchronized (this) {
        for (int i = 0; i < 5; i++) {
            System.out.println(i);
        }
    }
]

(二)、同期方法
同期メソッドはsynchronizedキーワード修飾メソッドで,Aスレッドがこのメソッドを実行することを保証する場合,他のスレッドはメソッド外で待つしかない.フォーマットの例は次のとおりです.
public synchronized void count() {
    for(int i = 0;i <5;i++) {
        System.out.println(i);
    }
}

四、グローバルロックとインスタンスロック
(一)、グローバルロック:このロックはクラスを対象とし、インスタンスの複数のオブジェクトにかかわらずスレッドがロックを共有します.グローバルロックにはstatic synchronized(またはクラスのclassまたはclassloaderオブジェクトにロック)が対応します.
(二)、サンプル・ロック:インスタンス・オブジェクトにロックされます.クラスが単一の場合、ロックにはグローバル・ロックの概念もあります.インスタンス・ロックにはsynchronizedキーワードが対応しています.
インスタンス・ロックとグローバル・ロックには、次のようなイメージのある例があります.
pulbic class Something {
    public synchronized void isSyncA(){}
    public synchronized void isSyncB(){}
    public static synchronized void cSyncA(){}
    public static synchronized void cSyncB(){}
}

Somethingには、xとyの2つのインスタンスがあるとします.次の4つの式で取得したロックの状況を分析します.
1、x.isSyncA()とx.isSyncB()
2、x.isSynca()とy.isSynca()
3、x.cSyncA()とy.cSyncB()
4、x.isSynca()とSomething.cSyncA()
1、同時にアクセスできない:isSynca()とisSyncB()は同じオブジェクト(オブジェクトx)にアクセスする同期ロックだから!
2、同時アクセス可能:同じオブジェクトの同期ロックではないので、x.isSyncA()はxの同期ロックにアクセスし、y.isSyncA()はyの同期ロックにアクセスする.
3、同時にアクセスできない:cSyncA()とcSyncB()はいずれもstaticタイプであるため、x.cSyncA()はSomethingに相当する.isSyncA(),y.cSyncB()はSomethingに相当する.isSyncB()は、同期ロックを共有しているため、同時にアクセスできません.
4、同時にアクセスできる:isSynca()はインスタンスメソッドであり、x.isSynca()はオブジェクトxのロックを使用する.一方、cSynca()は静的な方法です.cSyncA()は、「クラスのロック」を使用していることを理解できます.したがって、同時にアクセスできます.