jdkスレッドの同期問題

27229 ワード

一、銀行引き出しの問題
銀行がお金を引き出す例をシミュレートします.
public class ThreadDemo06 {

    public static void main(String[] args) {

        Bank bank = new Bank();

        Runnable runnable = new MoneyThread(bank);

        Thread thread1 = new Thread(runnable);

        Thread thread2 = new Thread(runnable);



        thread1.start();

        thread2.start();

    }

}



class Bank {

    private int money = 1000;



    public int get(int number) {

        if (number < 0) {

            return -1;

        } else if (number > money) {

            return -2;

        } else if (money < 0) {

            return -3;

        } else {

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            money -= number;



            System.err.println(" :" + money);

            return number;

        }

    }

}



class MoneyThread implements Runnable {

    private Bank bank;



    public MoneyThread(Bank bank) {

        this.bank = bank;

    }



    @Override

    public void run() {

        bank.get(800);

    }

}

実行可能な結果:
 :200-600

このような問題の根本的な原因は、複数のスレッドが共有データを操作したり、共有データを操作したりするスレッドコードに複数の行があり、1つのスレッドが共有データを操作する複数のコードの過程で、他のスレッドが演算に参加すると、スレッドのセキュリティ問題が発生することにある.
 
二、問題の解決方案
スレッドがリソースを使用するときにロックをかければいいです.リソースにアクセスする最初のスレッドがロックされた後、ロックが解除されない限り、他のスレッドはそのリソースを使用できません.同じ期間に1つのスレッドしか実行できません.他のスレッドは、このスレッドが完了するまで待機します.
複数の操作でデータを共有するスレッドコードをカプセル化し、スレッドがこれらのコードを実行している間に他のスレッドが演算に参加できない場合は、現在のスレッドがこれらのコードをすべて実行した後、他のスレッドが演算に参加しなければならない.
お金の引き出しと残高の修正が同時に完了することを保証します.
1)同期コードブロック,synchronized(obj){}を使用し,同期リスニングオブジェクトも必要である.
2)同期方法を用い、synchronizedを用いて同期を必要とする方法を修飾する.
方法1:同期方法
Javaでsynchronizedキーワードを使用してオブジェクトのロックを完了します.
上記のコードロックの解決策は以下の通りです.
class Bank {

    private int money = 1000;



    public synchronized int get(int number) {

        if (number < 0) {

            return -1;

        } else if (number > money) {

            return -2;

        } else if (money < 0) {

            return -3;

        } else {

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            money -= number;



            System.err.println(" :" + money);

            return number;

        }

    }

}

方法2:同期コードブロック
synchronizedブロックの書き方:
synchronized(object){
}//スレッドが実行時にobjectオブジェクトにロックされることを示す
ここでのobjectは任意のオブジェクトであってもよいが、複数のスレッドが持つこのobjectが同じオブジェクトであることを保証しなければならない.
synchronizedメソッドは粗粒度の同時制御であり、ある時点で、synchronizedメソッドを実行するスレッドは1つしかない.synchronizedブロックは細粒度の同時制御であり、ブロック内のコードを同期するだけであり、synchronizedブロック以外のコードは複数のスレッドによって同時にアクセスできる.
 
三、スレッド同期の重要な知識点
1)Javaの各オブジェクトにはロック(lock)またはモニタ(monitor)があり、あるオブジェクトのsynchronizedメソッドにアクセスすると、そのオブジェクトをロックすることを示す.この場合、他のスレッドはそのオブジェクトのsynchronizedメソッドにアクセスできなくなり、その前のスレッドの実行メソッドが完了するまで(または異常が投げ出された)、そのオブジェクトのロックを解放する.他のスレッドは、オブジェクトの他のsynchronizedメソッドにアクセスすることができます.
public class ThreadDemo07 {

    public static void main(String[] args) {

        Example example = new Example();

        Runnable runnable = new TheThread(example); // 



        Thread thread1 = new Thread(runnable);

        Thread thread2 = new Thread(runnable);



        thread1.start();

        thread2.start();

    }

}



class Example {

    public synchronized void execute() {

        for (int i = 0; i < 10; i++) {

            try {

                Thread.sleep((long) (Math.random() * 1000));

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println("execute :" + i);

        }

    }

}



class TheThread implements Runnable {

    private Example example;

    public TheThread(Example example) {

        this.example = example;

    }



    public void run() {

        example.execute();

    }

}

上記コードの実行:同じオブジェクトに対して生成されたスレッドであるため、2つの異なるスレッドにアクセスすると、synchronizedメソッドに先にアクセスするとそのexampleオブジェクトがロックされ、他のスレッドはそのオブジェクトの同期メソッドにアクセスできないため、1つのスレッドが実行されたか、異常が投げ出された後に2番目のスレッドにのみアクセスできる.
 
public class ThreadDemo08 {

    public static void main(String[] args) {

        Runnable runnable = new TheThread(new Example());

        Runnable runnable2 = new TheThread(new Example());



        Thread thread1 = new Thread(runnable);

        Thread thread2 = new Thread(runnable2);



        thread1.start();

        thread2.start();

    }

}



class Example {

    public synchronized void execute() {

        for (int i = 0; i < 10; i++) {

            try {

                Thread.sleep((long) (Math.random() * 1000));

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println("execute :" + i);

        }

    }

}



class TheThread implements Runnable {

    private Example example;

    public TheThread(Example example) {

        this.example = example;

    }



    public void run() {

        example.execute();

    }

}

上記のコードの実行:2つのスレッドが2つの異なるオブジェクトにアクセスするためです.では,この2つのスレッドには何の関連もなく,それぞれがそれぞれのオブジェクトにアクセスし,互いに干渉しない.
 
public class ThreadDemo09 {

    public static void main(String[] args) {

        Example example = new Example();

        Runnable runnable = new TheThread(example);// 



        Thread thread1 = new Thread(runnable);

        Thread thread2 = new Thread(runnable);



        thread1.start();

        thread2.start();

    }

}



class Example {

    public synchronized void execute() {

        for (int i = 0; i < 10; i++) {

            try {

                Thread.sleep((long) (Math.random() * 1000));

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println("execute :" + i);

        }

    }



public synchronized void execute2() {

        for (int i = 0; i < 10; i++) {

            try {

                Thread.sleep((long) (Math.random() * 1000));

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println("execute2 :" + i);

        }

    }

}



class TheThread implements Runnable {

    private Example example;

    public TheThread(Example example) {

        this.example = example;

    }



    public void run() {

        example.execute();

    }

}



class TheThread2 implements Runnable {

    private Example example;

    public TheThread2(Example example) {

        this.example = example;

    }



    public void run() {

        example.execute2();

    }

}

上記コードの実行結果:同じオブジェクトによって生成された2つの異なるスレッドであり、2つの異なるスレッドが同じオブジェクトの異なるsynchronizedメソッドにアクセスすると、最初のsynchronizedメソッドに先にアクセスすると、そのスレッドはそのオブジェクトをロックし、他のスレッドはそのオブジェクトのsynchronizedメソッドにアクセスできなくなる.
 
public class ThreadDemo10 {

    public static void main(String[] args) {

        Example example = new Example();

        Runnable runnable = new TheThread(example);



        Thread thread1 = new Thread(runnable);

        Thread thread2 = new Thread(runnable);



        thread1.start();

        thread2.start();

    }

}



class Example {

    public synchronized static void execute() {

        for (int i = 0; i < 10; i++) {

            try {

                Thread.sleep((long) (Math.random() * 1000));

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println("execute :" + i);

        }

    }



    public synchronized static void execute2() {

        for (int i = 0; i < 10; i++) {

            try {

                Thread.sleep((long) (Math.random() * 1000));

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println("execute2 :" + i);

        }

    }

}

上記のコードの実行結果:静的メソッドはクラスレベルに属するため、1つのメソッドがロックされた後、最初のスレッドが実行されてから2番目のスレッドに入るしかありません.
2)synchronizedメソッドはstaticキーワードを用いて修飾され,そのオブジェクトのClassオブジェクトをロックすることを示す.
public class ThreadDemo11 {

    public static void main(String[] args) {

        Runnable runnable = new TheThread(new Example());

        Runnable runnable2 = new TheThread(new Example());



        Thread thread1 = new Thread(runnable);

        Thread thread2 = new Thread(runnable2);



        thread1.start();

        thread2.start();

    }

}



class Example {

    public synchronized static void execute() {

        for (int i = 0; i < 10; i++) {

            try {

                Thread.sleep((long) (Math.random() * 1000));

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println("execute :" + i);

        }

    }



    public synchronized static void execute2() {

        for (int i = 0; i < 10; i++) {

            try {

                Thread.sleep((long) (Math.random() * 1000));

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println("execute2 :" + i);

        }

    }

}

上記コードの実行結果:2つの異なるオブジェクトに対して生成された異なるスレッドであるが、synchronizedメソッドではstaticキーワードを用いて修飾されているため、そのオブジェクトのClassオブジェクトをロックすることを示す.したがって、1つのスレッドが実行されると、他のスレッドがアクセスできるようになります.
3)1つのオブジェクトに複数のsynchronizedメソッドがあり、ある時点であるスレッドがsynchronizedメソッドに入った場合、そのメソッドが実行されないまで、他のスレッドはそのオブジェクトのsynchronizedメソッドにアクセスできません.
4)あるsynchronizedメソッドがstaticである場合、スレッドがこのメソッドにアクセスすると、そのロックはsynchronizedメソッドが存在するオブジェクトではなく、synchronizedメソッドが存在するオブジェクトに対応するClassオブジェクトである.Javaのいずれかのクラスに複数のオブジェクトがあるため、これらのオブジェクトは唯一のClassオブジェクトに対応するため、スレッドが同じクラスの2つのオブジェクトの2つのstatic、synchronizedメソッドにそれぞれアクセスする場合、彼らの実行順序にも順序があります.つまり、1つのスレッドが先に方法を実行し、実行が完了してから別のスレッドが実行を開始します.
public class ThreadDemo02 {

    public static void main(String[] args) {

        C c = new C();

        Thread t1 = new T1(c);

        Thread t2 = new T2(c);

        t1.start();



        try {

            Thread.sleep(500);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        t2.start();

    }

}



class C {

    public synchronized static void hello() {

        try {

            Thread.sleep(5000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println("hello");

    }

    public synchronized void world() {

        System.out.println("world");

    }

}



class T1 extends Thread {

    private C c;

    public T1(C c) {

        this.c = c;

    }

    @Override

    public void run() {

        c.hello();

    }

}



class T2 extends Thread {

    private C c;

    public T2(C c) {

        this.c = c;

    }

    @Override

    public void run() {

        c.world();

    }

}

実行結果:worldを実行してからhelloを出力します.staticは現在のオブジェクトのClassオブジェクトにロックされ、staticがないのは現在のオブジェクトにロックされ、2つのロックされたオブジェクトが異なるため、影響はありません.
 
四、スレッド同期総括
synchronized修飾方法
1)非静的メソッド:デフォルトの同期リスナーオブジェクトはthisです.
2)静的メソッド:デフォルトの同期Listenerオブジェクトは、メソッドが存在するクラスのClassオブジェクトです.
スレッドが実装されている場合
1)同期コードブロック:同期リスニングオブジェクトはthis、このメソッドが存在するクラスのClassオブジェクト、いずれかの不変オブジェクトを選択することができる.
2)同期メソッド:synchronizedを使用してrunメソッドを直接修飾できます.同期リスナーはthisです.
スレッドが継承されている場合
1)同期コードブロック:同期リスナーはこの方法が存在するクラスのClassオブジェクト、いずれかの不変オブジェクトを選択することができる.
2)同期メソッド:synchronizedを使用してrunメソッドを直接修飾することはできません.
3)まとめ:継承方式であれば、同期コードブロックでも同期方法でもthisは使用できません.
同期のメリットとデメリット
1)利点:スレッドのセキュリティ問題を解決した;
2)弊害:同期外のスレッドは同期ロックを判断するため、比較的効率が低下する.
3)前提:同期に複数のスレッドがあり、同じロックを使用する必要があります.