Javaマルチスレッド学習二synchronized

10365 ワード

前述したように、スレッド共有変数は非スレッドセキュリティであり、synchronizedキーワードはメソッドをスレッドセキュリティに変更する方法である.
一、スレッドセキュリティ問題
    private CountNum countNum;

    public MyThread(CountNum countNum) {
        this.countNum = countNum;
    }

    @Override
    public void run() {
        countNum.add("a");
    }
public class MyThread2 extends Thread {
    private CountNum countNum;

    public MyThread2(CountNum countNum) {
        this.countNum = countNum;
    }

    @Override
    public void run() {
       countNum.add("b");
    }
}

public class TestMain {
    public static void main(String[] args)  {
        CountNum countNum=new CountNum();
        MyThread myThread=new MyThread(countNum);
        MyThread2 myThread2=new MyThread2(countNum);
        myThread.start();
        myThread2.start();
    }
}

しゅつりょく
a set over
b set over
name:b   num:200
name:a   num:200

2つのスレッドが同時に1つの同期していない方法にアクセスし、aがnumの値を修正して睡眠したとき、bはこのときnumの値を修正し、a印刷時のnumの値は実際にbに修正された.これは共有変数がマルチスレッドの同僚にアクセスされたときの問題である.
二、synchronized
上記の問題を解決するのは簡単で、方法の前にsynchronizedキーワードを付けるだけでよく、aが眠っていてもbは方法実行に入らず、この場合方法は同期順に実行され、出力は以下の通りである.
a set over
name:a   num:100
b set over
name:b   num:200

三、複数オブジェクト複数ロック
TestMainコードを次のように変更します.
public class TestMain {
    public static void main(String[] args)  {
        CountNum countNum=new CountNum();
        CountNum countNum2=new CountNum();
        MyThread myThread=new MyThread(countNum);
        MyThread2 myThread2=new MyThread2(countNum2);
        myThread.start();
        myThread2.start();
    }
}
a set over
b set over
name:b   num:200
name:a   num:100

各スレッドは異なるオブジェクトで、synchronizedのロックが異なるオブジェクトであるため、コードは非同期で実行されます.synchronizedキーワードで取得したロックはすべてオブジェクトロックです.注意:Aスレッドがオブジェクトのロックを取得した場合、他のスレッドは、そのオブジェクトの非synchronizedタイプのメソッドを非同期で呼び出すことができますが、他のスレッドが他のsynchronizedメソッドを呼び出す場合は待機する必要があります.
四、汚い読み
public class PublicVar {
    private String name="b";
    private String passwd="bb";

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

    public String getValue() {
        System.out.println("getName "+Thread.currentThread().getName()+" name="+name+" passwd="+passwd);
        return name;
    }
}

public class MyThread extends Thread {

    private PublicVar publicVar;

    public MyThread(PublicVar publicVar) {
        this.publicVar = publicVar;
    }

    @Override
    public void run() {
        publicVar.setValue("a", "aa");
    }
}

    public static void main(String[] args) throws InterruptedException {
        PublicVar publicVar=new PublicVar();
        MyThread myThread=new MyThread(publicVar);
        myThread.start();
        Thread.sleep(2000);
        publicVar.getValue();
    }
getName main name=a passwd=bb
setName Thread-0 name=a passwd=aa

ここでダーティリードが発生したのはgetValueメソッドが同期していないためであり,メインスレッドがthreadスレッドを起動してから2秒休止し,threadスレッドはnameの値を設定してから5秒休止し,メインスレッドが目が覚めてからgetValueメソッドを実行し続け,休止した2秒の間にnameはthreadスレッドに新しい値を設定されたがpasswdはまだないためである.この問題を解決する方法はもちろんgetValueメソッドにも同期synchronizedキーワードを付けることです
まとめ:Aスレッドがanyobjectオブジェクトにsynchronizedキーワードを加えたXメソッドを呼び出すと、AスレッドはXメソッドロックを取得し、より正確には、オブジェクトのロックを取得するので、他のスレッドはAスレッドが実行されるまでXメソッドを呼び出すことはできませんが、他のスレッドは他のsynchronized以外のメソッドを呼び出すことができます.他のスレッドがsynchronizedキーワードを宣言する非Xメソッドを呼び出す場合も、AスレッドがXメソッドを実行するのを待つ必要があります.つまり、オブジェクトロックを解放してから呼び出すことができます.
五、synchronizedロック再入
再読み込み可能ロック:たとえば、Aスレッドがオブジェクトのロックを取得した場合、このオブジェクトロックはまだ解放されていません.このオブジェクトのロックを再び取得したい場合は、synchronizedメソッド/ブロックの内部で本クラスまたは親クラスの他のsynchronizedメソッド/ブロックを呼び出すと、ロックは永遠に得られます.鍵をかけて再入できないとデッドロックになります.親子クラス継承関係がある場合、子クラスは親クラスの同期メソッドを再ロック可能に呼び出すことができます.注意:
親クラスを書き換えるsynchronizedメソッドは同期効果がありません.同期する必要がある場合はsynchronizedキーワードを手動で追加します.
六、デッドロック
public class DealThread implements Runnable {

    public String name;
    public Object lock1 = new Object();
    public Object lock2 = new Object();

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        if ("a".equals(name)) {
            synchronized (lock1) {
                try {
                    System.out.println("name=" + name);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println(" lock1->lock2       ");
                }
            }
        }
        if ("b".equals(name)) {
            synchronized (lock2) {
                try {
                    System.out.println("name=" + name);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println(" lock2->lock1       ");
                }
            }
        }
    }
}

    public static void main(String[] args) throws InterruptedException {
        DealThread dealThread=new DealThread();
        dealThread.setName("a");
        Thread thread=new Thread(dealThread);
        thread.start();
        Thread.sleep(1000);
        dealThread.setName("b");
        Thread thread2=new Thread(dealThread);
        thread2.start();
    }

デッドロック情報を見て、jdkのbinディレクトリに入ってjpsコマンドを実行し、次のように出力します.TestMainスレッドのidは8196であることがわかります.
3904 Jps
10628 Launcher
8196 TestMain
9892 RemoteMavenServer
11432

jstackコマンドを使用して結果を表示jstack -l 8196
Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x0000000002f1c978 (object 0x00000000d59a1d30, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x0000000002f188d8 (object 0x00000000d59a1d40, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.yjj.chapter02.test04_syn4.DealThread.run(DealThread.java:43)
        - waiting to lock <0x00000000d59a1d30> (a java.lang.Object)
        - locked <0x00000000d59a1d40> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)
"Thread-0":
        at com.yjj.chapter02.test04_syn4.DealThread.run(DealThread.java:30)
        - waiting to lock <0x00000000d59a1d40> (a java.lang.Object)
        - locked <0x00000000d59a1d30> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

七、ロック対象の変更
public class MyService {
    private String lock="123";

    public void testMethod(){
        synchronized (lock){
            try {
                System.out.println(Thread.currentThread().getName()+" begin "+System.currentTimeMillis());
                lock="456";
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName()+" end "+System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadA extends Thread{
    private MyService myService;

    public ThreadA(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run() {
        myService.testMethod();
    }
}

ThreadAとThreadB
public class TestMain {
    public static void main(String[] args) throws InterruptedException {
        MyService service=new MyService();
        ThreadA a =new ThreadA(service);
        a.setName("A");
        ThreadB b =new ThreadB(service);
        b.setName("B");
        a.start();
        Thread.sleep(50);
        b.start();
    }
}

AスレッドとBスレッドは非同期で実行されていることがわかり、synchronizedキーが付けられているが、Aスレッドが競合しているのはロックが「123」であるため、ロックを得た後にロックを「456」に変更し、Bスレッドが競合しているのは「456」であり、このときスレッドが「456」のロックを占有していないため、Bスレッドもロックを得て、次のような状況になっている.
A begin 1537950658147
B begin 1537950658196
A end 1537950660147
B end 1537950660196

オブジェクトが変化しない限り、オブジェクトのプロパティが変更されても実行結果は同期されます.つまり、1つのUserオブジェクトをロックと見なし、userのnameプロパティが変化しても同じuserであれば同期して実行されます.
八、volatileキーワード
volatileについて後で1篇の専門の文章を書くようにしましょう、今ただvolatileが可視性、秩序性だけを保証することを知っているだけで、原子性を保証しないで、i++は1つの原子の操作ではありません
九、原子類がうまく使えなくても完全に安全ではない
public class MyService {
    public static AtomicLong atomicLong=new AtomicLong();

    public void addNum(){
        System.out.println(Thread.currentThread().getName()+"  100  ="+atomicLong.addAndGet(100));
        atomicLong.addAndGet(1);
    }
}
    @Override
    public void run() {
        myService.addNum();
    }
    public static void main(String[] args) {
        MyService myService=new MyService();
        AddCountThread addCountThread=new AddCountThread(myService);
        Thread t1=new Thread(addCountThread);
        Thread t2=new Thread(addCountThread);
        Thread t3=new Thread(addCountThread);
        Thread t4=new Thread(addCountThread);
        Thread t5=new Thread(addCountThread);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
Thread-1  100  =100
Thread-3  100  =300
Thread-2  100  =200
Thread-4  100  =403
Thread-5  100  =503

出力結果は以上のように,我々が望む結果ではないことが分かったが,これはaddAndGet()メソッド呼び出しが原子ではないためであり,このメソッドは原子メソッドであるが,2つのaddAndGet()メソッド間の呼び出しが原子ではないため,この問題を解決するには同期を用いなければならない.addNumメソッド上でsynchronizedまたは同期コードブロックを用いて2つのaddAndGet()メソッドの外に含む