web-dev-qa-db-ja.com

同期ブロックにパラメーターを渡す目的は何ですか?

そんなこと知ってる

コードのブロックを同期するときは、ロックとして使用するオブジェクトのロックを指定するため、たとえば、このコードのロックとしてサードパーティのオブジェクトを使用できます。これにより、1つのオブジェクト内でコード同期のために複数のロックを設定できます。

しかし、ブロックに引数を渡す必要性を理解していません。 Stringのインスタンスを渡すかどうかは関係ないため、同期ブロックはパラメーターに渡されるパラメーターに関係なく完全に機能するため、一部のランダムクラスのインスタンスを同期ブロックに渡します。

だから私の質問はとにかく同期ブロックが2つのスレッドが同時にクリティカルセクションに入るのを止めるかどうかです。では、なぜ引数を渡す必要があるのでしょうか。 (私はデフォルトでいくつかのランダムなオブジェクトのロックを取得することを意味します)。

私は私の質問を正しく組み立てることを望みます。

ランダムなパラメーターを同期ブロックに送ることで、次の例を試しました。

public class Launcher {

    public static void main(String[] args) {
        AccountOperations accOps=new AccountOperations();

        Thread lucy=new Thread(accOps,"Lucy");
        Thread sam=new Thread(accOps,"Sam");

        lucy.start();
        sam.start();

    }

}

非静的同期ブロックの使用:

public class AccountOperations implements Runnable{
    private  Account account = new Account();


    public void run(){

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

            makeWithdrawal(10);                 
        }
    }

    public  void makeWithdrawal(int amount){
        String str="asd"
        synchronized (str /* pass any non-null object the synchronized block works*/) {
            if(account.getAmount()>10){

                try{
                    Thread.sleep(5000);             
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                account.withdraw(amount);
                System.out.println(Thread.currentThread().getName()+" has withdrawn 10, current balance "+ account.getAmount());
            }else{
                System.out.println("Insufficient funds "+account.getAmount());
            }
        }

    }

}

静的同期ブロックの使用:

public class AccountOperations implements Runnable{
    private static Account account = new Account();


    public void run(){

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

            makeWithdrawal(10);                 
        }
    }

    public static void makeWithdrawal(int amount){

        synchronized (String.class /* pass any class literal synchronized block works*/) {
            if(account.getAmount()>10){

                try{
                    Thread.sleep(5000);             
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                account.withdraw(amount);
                System.out.println(Thread.currentThread().getName()+" has withdrawn 10, current balance "+ account.getAmount());
            }else{
                System.out.println("Insufficient funds "+account.getAmount());
            }
        }

    }

}
19
John Rambo

とにかく同期ブロックは、2つのスレッドが同時にクリティカルセクションに入るのを停止します。では、なぜ引数を渡す必要があるのでしょうか。

同期ブロックは、渡されたオブジェクトに基づいて停止するスレッドを決定します。渡したオブジェクトは、同期ブロックによって保護されたクリティカルセクションの識別子として機能します。

プログラムには多くの重要なセクションがあり、そのすべてを互いに同時に実行することができます。たとえば、同時にアクセスする必要のある2つの無関係なコレクションがある場合、コレクションごとに個別のクリティカルセクションを設定できます。この方法では、他のスレッドがすでに同じコレクションにアクセスしている場合にのみ、スレッドが停止します。 2つの異なるコレクションにアクセスする2つの異なるスレッドは、同時に進行することができます。

最初の例は簡単ではありません。これが機能する理由は、文字列オブジェクトが文字列リテラルに初期化されるためです。リテラルのインターンにより、関数に入るすべてのスレッドは同じStringオブジェクトを取得するため、同期ブロックはクリティカルセクションを適切に保護します。

7
dasblinkenlight

Stringのインスタンスを渡すかどうかは関係ないため、同期ブロックはパラメーターに渡されるパラメーターに関係なく完全に機能するため、一部のランダムクラスのインスタンスを同期ブロックに渡します。

パラメータの目的は2つあります。

  1. 同じオブジェクトのotherブロックを同期することができるため、同じオブジェクトの状態を変更する可能性のあるコードの2つのブロックがある場合、それらは干渉しませんお互い。

    例えば:

    public void getSum() {
        int sum = 0;
        synchronized (this.list) {
            for (Thingy t : this.list) {
                sum += t.getValue();
            }
        }
        return sum;
    }
    
    public void addValue(int value) {
        synchronized (this.list) {
            this.list.add(new Thingy(value));
        }
    }
    

    そこで、スレッド間でlistへのbothアクセスを同期することが重要です。別のスレッドがaddValueを呼び出している間は、getSumを呼び出してリストを踏むことはできません。

  2. これにより、正しい粒度で同期していることを確認できます。インスタンス固有のリソースへのアクセスをシリアル化する場合、インスタンス間でそれを行うのは意味がありません。異なるインスタンスで動作している場合は、ブロックへの複数のスレッドを許可する必要があります。これが、インスタンス固有のリソースのthis(またはより一般的にはthisのいくつかのフィールド)、または静的リソースの場合はクラス(またはより一般的にはいくつかのクラスフィールド)で同期する理由です。同様に、特定のフィールドのみを保護する必要がある場合は、thisで同期する必要はありません。

    例えば:

    // (In MyClass)
    
    public void getThingySum() {
        int sum = 0;
        synchronized (this.thingyList) {
            for (Thingy t : this.thingyList) {
                sum += t.getValue();
            }
        }
        return sum;
    }
    
    public void addThingy(Thingy t) {
        synchronized (this.thingyList) {
            this.thingyList.add(t);
        }
    }
    
    public void getNiftySum() {
        int sum = 0;
        synchronized (this.niftyList) {
            for (Nifty n : this.niftyList) {
                sum += n.getValue();
            }
        }
        return sum;
    }
    
    public void addNifty(Nifty n) {
        synchronized (this.niftyList) {
            this.niftyList.add(t);
        }
    }
    

    そこで、thisthis.thingyListではなく、this.thingyList上のMyClass.classへのアクセスを同期します。あるスレッドがgetThingySumを呼び出している間に別のスレッドがaddNiftyを呼び出している場合は問題ありません。そのため、thisでの同期はやりすぎです。


strの例をもう一度:

public  void makeWithdrawal(int amount){
    String str="asd"
    synchronized (str /* pass any non-null object the synchronized block works*/) {
        if(account.getAmount()>10){

            try{
                Thread.sleep(5000);             
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            account.withdraw(amount);
            System.out.println(Thread.currentThread().getName()+" has withdrawn 10, current balance "+ account.getAmount());
        }else{
            System.out.println("Insufficient funds "+account.getAmount());
        }
    }

}

コメントは正しくありません。null以外のインスタンスは、そのコードを適切に保護しません。上記が機能するように見える理由は、string interningです。文字列リテラルは自動的に文字列に入れられるため、同じStringインスタンスがすべてのスレッドで使用されます intern プール。 (これは、過度に同期していることを意味します。これは、インスタンス固有ではなく、JVM全体に適用されます。)したがって、それは機能しますが、単なるオブジェクトではないためです。変更した場合:

String str = "asd";

Object o = new Object();

その上で同期すると、アカウントへのアクセスをシリアル化することは何も行われません。

あなたの例では、同期する正しいものはthis.accountです。

25
T.J. Crowder