【Java同時プログラミング】2.1オブジェクトおよび変数の同時アクセス——synchronized同期方法

16614 ワード

前言


以下の技術点を重点的に把握する:1.synchronizedオブジェクトモニタはObjectの使用です.2.synchronizedオブジェクトモニタがClassの場合の使用3.非スレッドセキュリティ時にどのように現れるか.4.シャットダウンワードvolatileの主な役割;5.キーワードvolatileとsynchronizedの違いと使用状況.

synchronized同期方法


非スレッドセキュリティは、同じオブジェクト内のインスタンス変数に複数のスレッドが同時アクセスすると発生し、結果として「ダーティリード」、つまり取得したデータが変更されます.スレッドセキュリティは、取得したインスタンス変数の値が同期処理され、汚れたスレッドは表示されません.

2.1.1メソッド内の変数はスレッドセキュリティ


非スレッドセキュリティの問題はインスタンス変数に存在し、メソッド内部のプライベート変数であれば非スレッドセキュリティの問題は存在しないため、結果もスレッドセキュリティです.次の例の実装方法では、非スレッドセキュリティの問題は存在しない変数を内部的に宣言します.
public class HasSelfPrivateNum {

    public void addI(String userName) {
        int num;
        if(userName.equals("a")){
            num=100;
            System.out.println("a set off");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            num=200;
            System.out.println("b set off");
        }
        System.out.println(userName+" num="+num);
    }
}

class ThreadA extends Thread {

    private HasSelfPrivateNum numRef;

    public ThreadA(HasSelfPrivateNum numRef) {
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        numRef.addI("a");
    }
}

class ThreadB extends Thread {

    private HasSelfPrivateNum numRef;

    public ThreadB(HasSelfPrivateNum numRef) {
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        numRef.addI("b");
    }
}

class Run{
    public static void main(String[] args) {
        HasSelfPrivateNum numRef = new HasSelfPrivateNum();
        ThreadA threadA = new ThreadA(numRef);
        threadA.start();
        ThreadB threadB = new ThreadB(numRef);
        threadB.start();
    }
}

結果:
b set off
b num=200
a set off
a num=100

可視メソッドの内部変数numが実行する操作には、非スレッドセキュリティの問題はありません.

2.1.2インスタンス変数非スレッドセキュリティ


複数のスレッドが1つのオブジェクトのインスタンス変数に共通してアクセスしている場合、「非スレッドセキュリティの問題」が発生する可能性があります.スレッドでアクセスするオブジェクトに複数のインスタンス変数がある場合、実行結果が交差する可能性があります.オブジェクトにインスタンス変数が1つしかない場合は、オーバーライドが発生する可能性があります.
/**
 *  num 
 * @author Arthur
 * @date 2017-12-25 14:15
 */

public class HasSelfPrivateNum {

    private int num =0;

    public void addI(String userName) {
        if(userName.equals("a")){
            num=100;
            System.out.println("a set off");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            num=200;
            System.out.println("b set off");
        }
        System.out.println(userName+" num="+num);
    }
}

class ThreadA extends Thread {

    private HasSelfPrivateNum numRef;

    public ThreadA(HasSelfPrivateNum numRef) {
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        numRef.addI("a");
    }
}

class ThreadB extends Thread {

    private HasSelfPrivateNum numRef;

    public ThreadB(HasSelfPrivateNum numRef) {
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        numRef.addI("b");
    }
}

class Run{
    public static void main(String[] args) {
        HasSelfPrivateNum numRef = new HasSelfPrivateNum();
        ThreadA threadA = new ThreadA(numRef);
        threadA.start();
        ThreadB threadB = new ThreadB(numRef);
        threadB.start();
    }
}
a set off
b set off
b num=200
a num=200

この例では、2つのスレッドが同期を使用しない方法に同時にアクセスし、2つのスレッドがビジネス・オブジェクトのインスタンス変数を同時に操作すると、「非スレッド・セキュリティ」の問題が発生します.解決策は、synchronizedキーワードをメソッドの前に追加すればよい.2つのスレッドが同じオブジェクトの同期メソッドにアクセスする場合は、スレッドが安全である必要があります.

2.1.3複数のオブジェクトの複数のロック

public class HasSelfPrivateNum {

    private int num = 0;

    synchronized  public void addI(String userName) {
        if(userName.equals("a")){
            num=100;
            System.out.println("a set off");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            num=200;
            System.out.println("b set off");
        }
        System.out.println(userName+" num="+num);
    }
}

class ThreadA extends Thread {

    private HasSelfPrivateNum numRef;

    public ThreadA(HasSelfPrivateNum numRef) {
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        numRef.addI("a");
    }
}

class ThreadB extends Thread {

    private HasSelfPrivateNum numRef;

    public ThreadB(HasSelfPrivateNum numRef) {
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        numRef.addI("b");
    }
}

class Run{
    public static void main(String[] args) {
        HasSelfPrivateNum numRef = new HasSelfPrivateNum();
        HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
        ThreadA threadA = new ThreadA(numRef);
        threadA.start();
        ThreadB threadB = new ThreadB(numRef2);
        threadB.start();
    }
}
a set off
b set off
b num=200
a num=100

上記のコードは2つのHasSelfPrivateNumインスタンスを作成したため、2つのロックがあり、方法は同期されているが、結果は非同期である.bの値を先に印刷してからaの値を印刷する効果があります.どうしてそうなの?synchronizedキーワードは、メソッドやコードブロックをロックするのではなく、オブジェクトをロックすることです.上記の例では、synchronized付きメソッドを先に実行するスレッドは、そのメソッドが属するオブジェクトのロックロックロックを先に取得し、複数のスレッドが同じオブジェクトにアクセスすることを前提として、他のスレッドが待機します.ただし、複数のスレッドが複数のオブジェクトにアクセスすると、JVMは複数のロックを作成します.上記の問題はそうである.

2.1.4 synchronizedメソッドとロックオブジェクト


スレッドロックがオブジェクトであることを確認するには、次のコードを使用します.
/**
 *  
 *
 * @author Arthur
 * @date 2017-12-26 16:08
 */

public class SynchronizedMethodLock {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        MyThreadA myThreadA = new MyThreadA(myObject);
        MyThreadB myThreadB = new MyThreadB(myObject);
        myThreadA.setName("A");
        myThreadB.setName("B");
        myThreadA.start();
        myThreadB.start();
    }
}

class MyObject{
    public void method() throws InterruptedException {
        System.out.println("begin threadName:"+Thread.currentThread().getName());
        Thread.sleep(5000);
        System.out.println("end");
    }
}
class MyThreadA extends Thread{

    private MyObject myObject;

    public MyThreadA(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        super.run();
        try {
            myObject.method();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class MyThreadB extends Thread{

    private MyObject myObject;

    public MyThreadB(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        super.run();
        try {
            myObject.method();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

実行結果は
begin threadName:A
begin threadName:B
A end
B end

method()メソッドがsynchronizedで修飾される場合
public synchronized void method() 

実行結果は
begin threadName:A
A end
begin threadName:B
B end

キーワードsynchronized宣言を呼び出す方法は、必ずキューに並んで実行されます.また、同期化処理は共有リソースの読み書きアクセスのみが必要です!他のメソッドは呼び出されたときにどのような効果がありますか?ロックロックオブジェクトの効果を確認するにはどうすればいいですか?更新コードは次のとおりです.
/**
 *  
 *
 * @author Arthur
 * @date 2017-12-26 16:08
 */

public class SynchronizedMethodLock {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();
        MyThreadA myThreadA = new MyThreadA(myObject);
        MyThreadB myThreadB = new MyThreadB(myObject);
        myThreadA.setName("A");
        myThreadB.setName("B");
        myThreadA.start();
        myThreadB.start();
    }
}

class MyObject{
    public /*synchronized*/ void method() throws InterruptedException {
        System.out.println("begin threadName:"+Thread.currentThread().getName());
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName()+" end");
    }
}
class MyThreadA extends Thread{

    private MyObject myObject;

    public MyThreadA(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        super.run();
        try {
            myObject.method();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class MyThreadB extends Thread{

    private MyObject myObject;

    public MyThreadB(MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        super.run();
        try {
            myObject.method();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

結果は次のとおりです.
begin threadName:A
begin threadName:B
A end
B end

methodAが同期メソッドであっても、スレッドAはmyObjectオブジェクトのロックを保持しているが、スレッドBは非synchronizedメソッドmethodBを非同期で呼び出すことができることがわかる.試験を続けmethodB法にsynchronizedを加える
public synchronized void methodB() 

実行結果は順番に実行されます.
begin methodA threadName:A
A end
begin methodB threadName:B
B end

これにより,1)Aスレッドはobjectオブジェクトのロックを先に持ち,Bスレッドはobjectオブジェクト中のsynchronized以外のメソッドを非同期で呼び出すことができると結論した.2)Aスレッドはまずobjectオブジェクトのロックを持ち,Bスレッドはこのときobjectオブジェクトのsynchronizedのメソッドを呼び出すと待機,すなわち同期が必要となる.

2.1.5ダーティリード


複数のスレッドで同じメソッドを呼び出す場合、データが交差することを避けるためにsynchroinizedで同期することについて説明した.ただし、割り当て時に同期した操作もありますが、値を取るときに予期せぬ事故が発生する可能性があります.これはダーティリードです.ダーティリードが発生したのは、インスタンス変数を読み込むときに、この値が他のスレッドによって変更された場合です.
/**
 *  
 *
 * @author Arthur
 * @date 2017-12-26 17:07
 */

public class Run {
    public static void main(String[] args) throws InterruptedException {
        PublicVar varRef = new PublicVar();
        ThreadA threadA = new ThreadA(varRef);
        threadA.start();
        threadA.setName("threadA");
        Thread.sleep(200);// 
        varRef.getValue();
    }
}
class PublicVar{
    public String username = "A";
    public String password = "AA";

    synchronized public void setValue(String username, String password) {
        try {
            this.username = username;
            Thread.sleep(5000);
            this.password = password;
            System.out.println("setValue method threadName="+Thread.currentThread().getName()+" username="+username+" password="+password);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void getValue() {
        System.out.println("getValue method threadName="+Thread.currentThread().getName()+" username="+username+" password="+password);
    }
}

class ThreadA extends Thread{
    private PublicVar var;

    public ThreadA(PublicVar var) {
        this.var = var;
    }

    @Override
    public void run() {
        super.run();
        var.setValue("B","BB");
    }
}

プログラムは次のように実行されます.
getValue method threadName=main username=B password=AA
setValue method threadName=threadA username=B password=BB

ダーティリードが発生したのはpublic void getValue()メソッドが同期していないため、いつでも呼び出すことができます.解決策は同期キーを付けることです
synchronized public void getValue() {
        System.out.println("getValue method threadName="+Thread.currentThread().getName()+" username="+username+" password="+password);
    }

プログラムは正常に動作し、setValueとgetValueが順次実行されます.
setValue method threadName=threadA username=B password=BB
getValue method threadName=main username=B password=BB

まとめ:1.AスレッドがanyObjectのsynchronizedのXメソッドを呼び出すと、Xメソッドロックが取得され、より正確にはanyObjectのオブジェクトロックであるため、他のスレッドはAスレッドがXメソッドを呼び出すまでXメソッドを実行しなければならないが、他のスレッドはsynchronized以外のメソッドを呼び出すことができる.2.AスレッドがanyObjectのsynchronizedのXメソッドを呼び出すと、Xメソッドロックが取得され、より正確にはanyObjectのオブジェクトロックであるため、他のスレッドはAスレッドがXメソッドを呼び出すまで待たなければならないが、他のスレッドB呼び出しがsynchronizedの非Xメソッドを宣言した場合、オブジェクトロックがAスレッドに保持されるため、AスレッドがXメソッドを実行した後にsynchronizedの非Xメソッドを実行する必要がある.このような状況では汚れた読み現象は現れない.

2.1.6ロック再入


synchronizedはロック再入機能を有し、一般的に、あるスレッドがオブジェクトロックを取得した後、このオブジェクトロックがここでオブジェクトのロックを得ることができることを要求する.これはまた、1つのsynchronizedメソッド/ブロック内で本クラスを呼び出す他のsynchronizedメソッド/ブロックが永遠にロックされることを証明する.コードを見てください:
/**
 *  
 * @author Arthur
 * @date 2017-12-26 17:25
 */

public class Run {
    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        threadA.start();
    }
}

class Service{

    public synchronized void service1() {
        System.out.println("service1.");
        service2();
    }

    public synchronized void service2(){
        System.out.println("service2.");
        service3();
    }

    public synchronized void service3(){
        System.out.println("service3.");
    }

}

class ThreadA extends Thread{

    @Override
    public void run() {
        super.run();
        Service service = new Service();
        service.service1();
    }
}

結果は
service1.
service2.
service3.

再読み込み可能な概念:スレッドがオブジェクトロックを持っている場合、このオブジェクトロックはまだ解放されておらず、ロックの取得を再要求した場合でも取得できます.再入できないとデッドロックになります.再ロックは、親子クラスでの呼び出しもサポートします.

2.1.7異常が発生し、ロックが自動的に解除される


スレッドが例外を実行すると、その保持するロックは自動的に解放されます.

2.1.8同期に継承性がない

/**
 *  
 *
 * @author Arthur
 * @date 2017-12-26 17:37
 */

public class Run {
    public static void main(String[] args) {
        Sub subRef = new Sub();
        ThreadA threadA = new ThreadA(subRef);
        threadA.setName("A");
        threadA.start();
        ThreadB threadB = new ThreadB(subRef);
        threadB.setName("B");
        threadB.start();
    }
}

class Main{
    synchronized public void sleep(){
        try {
            System.out.println(" Main   sleep begin thread name="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println(" Main   sleep end thread name="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Sub extends Main{
    @Override
    public void sleep(){
        super.sleep();
        try {
            System.out.println(" Sub   sleep begin thread name="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println(" Sub   sleep end thread name="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadA extends Thread{

    private Sub sub;

    public ThreadA(Sub sub) {
        this.sub = sub;
    }

    @Override
    public void run() {
        super.run();
        sub.sleep();
    }
}

class ThreadB extends Thread{

    private Sub sub;

    public ThreadB(Sub sub) {
        this.sub = sub;
    }

    @Override
    public void run() {
        super.run();
        sub.sleep();
    }
}

実行結果:
 Main   sleep begin thread name=A time=1514281689722
 Main   sleep end thread name=A time=1514281694722
 Main   sleep begin thread name=B time=1514281694722 
 Sub   sleep begin thread name=A time=1514281694722
 Main   sleep end thread name=B time=1514281699723
 Sub   sleep begin thread name=B time=1514281699723
 Sub   sleep end thread name=A time=1514281699723
 Sub   sleep end thread name=B time=1514281704723

結果から、同期は継承されていないことがわかります.サブクラスのメソッドにsynchronizedを付ける
public synchronized void sleep()

プログラムは正常に動作しています.
 Main   sleep begin thread name=B time=1514281920201
 Main   sleep end thread name=B time=1514281925201
 Sub   sleep begin thread name=B time=1514281925201
 Sub   sleep end thread name=B time=1514281930201
 Main   sleep begin thread name=A time=1514281930201
 Main   sleep end thread name=A time=1514281935202
 Sub   sleep begin thread name=A time=1514281935202
 Sub   sleep end thread name=A time=1514281940202

次のセクションでは、2.2 synchronized同期文ブロックについて説明します.