オブジェクトまたはメソッドのJava同期メソッドロック?


191

同じクラスに2つの同期されたメソッドがあり、それぞれが異なる変数にアクセスしている場合、2つのスレッドがそれらの2つのメソッドに同時にアクセスできますか?ロックはオブジェクトで発生しますか、それとも同期メソッド内の変数と同じくらい具体的になりますか?

例:

class X {

    private int a;
    private int b;

    public synchronized void addA(){
        a++;
    }

    public synchronized void addB(){
        b++;
    }

}

2つのスレッドが実行クラスXの同じインスタンスにアクセスすることができますx.addA()とx.addB()同じ時間に?

回答:


197

(と入力して行うように)メソッドを同期として宣言すると、オブジェクト全体でpublic synchronized void addA()同期するため、この同じオブジェクトから異なる変数にアクセスする2つのスレッドが互いにブロックします。

一度に1つの変数のみを同期する場合、2つのスレッドが異なる変数にアクセスしているときに互いにブロックされないようにするには、synchronized ()ブロックで個別に同期します。オブジェクト参照の場合ab次を使用します。

public void addA() {
    synchronized( a ) {
        a++;
    }
}

public void addB() {
    synchronized( b ) {
        b++;
    }
}

しかし、それらはプリミティブなので、これを行うことはできません。

代わりにAtomicIntegerを使用することをお勧めします。

import java.util.concurrent.atomic.AtomicInteger;

class X {

    AtomicInteger a;
    AtomicInteger b;

    public void addA(){
        a.incrementAndGet();
    }

    public void addB(){ 
        b.incrementAndGet();
    }
}

181
メソッドで同期をとると、オブジェクト全体がロックされるため、この同じオブジェクトから異なる変数にアクセスする2つのスレッドが互いにブロックします。 それは少し誤解を招きやすいです。メソッドの同期は、メソッドsynchronized (this)の本体の周りにブロックを置くことと機能的に同等です。オブジェクト「this」はロックされません。むしろ、オブジェクト「this」がミューテックスとして使用され、ボディは「this」で同期された他のコードセクションと同時に実行されるのを防ぎます。同期されていない「this」の他のフィールド/メソッドには影響しません。
Mark Peters、

13
はい、それは本当に誤解を招くものです。実際の例-これを見てください-stackoverflow.com/questions/14447095/…-概要:ロックは同期されたメソッドレベルでのみ行われ、オブジェクトのインスタンス変数は他のスレッドからアクセスできます
mac

5
最初の例は根本的に壊れています。場合abオブジェクトである、例えばIntegerS、あなたがしているインスタンスで同期された別のオブジェクトに置き換え適用する際に++オペレータを。
Holger

答えを修正して、AtomicIntegerを初期化します。AtomicInteger a = new AtomicInteger(0);
Mehdi

多分このanwserは、オブジェクト自体での同期について、この別の1つで説明されているように更新する必要があります:stackoverflow.com/a/10324280/1099452
lucasvc

71

メソッド宣言で同期されるのは、このための構文上の砂糖です。

 public void addA() {
     synchronized (this) {
          a++;
     }
  }

静的メソッドでは、これは構文上の砂糖です。

 ClassA {
     public static void addA() {
          synchronized(ClassA.class) {
              a++;
          }
 }

Javaデザイナーが同期について現在理解していることを知っていれば、同時実行性の悪い実装につながることが多いため、構文上の砂糖を追加しなかったと思います。


3
違います。synchronizedメソッドは、synchronized(object)とは異なるバイトコードを生成します。機能は同等ですが、単なる構文上の糖衣ではありません。
Steve Kuo

10
「構文上の砂糖」がバイトコードの同等物として厳密に定義されているとは思いません。ポイントは機能的には同等です。
Yishai

1
Java設計者がモニターについてすでに知られていることを知っていれば、基本的にUnixの内部をエミュレートする代わりに、異なる方法で行ったはずです。ブランチごとのハンセン氏は、Java同時実行プリミティブを見て、「明らかに私は無駄に働いた」と語った
ローン侯爵2013

これは本当です。OPの例では、各メソッドをロックしているように見えますが、実際にはすべて同じオブジェクトをロックしています。非常に不正な構文。Javaを10年以上使用した後、私はこれを知りませんでした。したがって、このため、同期メソッドは避けます。常に同期で定義されたメソッドごとに不可視オブジェクトが作成されると思っていました。
ピーターQuiring

21

同期メソッドの「Java™チュートリアル」から:

まず、同じオブジェクト上で同期メソッドの2つの呼び出しをインターリーブすることはできません。1つのスレッドがオブジェクトの同期メソッドを実行しているとき、同じオブジェクトの同期メソッドを呼び出す他のすべてのスレッドは、最初のスレッドがオブジェクトで完了するまでブロックします(実行を中断します)。

同期ブロックの「Java™チュートリアル」から:

同期ステートメントは、きめの細かい同期で並行性を改善する場合にも役立ちます。たとえば、クラスMsLunchに2つのインスタンスフィールドc1とc2があり、一緒に使用されることはないとします。これらのフィールドのすべての更新を同期する必要がありますが、c1の更新がc2の更新とインターリーブされるのを防ぐ理由はありません。そうすることで、不要なブロックを作成して同時実行性を減らします。同期メソッドを使用するか、またはこれに関連付けられたロックを使用する代わりに、ロックを提供するためだけに2つのオブジェクトを作成します。

(エンファシス鉱山)

2つの非インターリーブ変数があるとします。したがって、異なるスレッドからそれぞれに同時にアクセスする必要があります。ロックはオブジェクトクラス自体ではなく、以下のようなObjectクラスで定義する必要があります(2番目のOracleリンクの例)。

public class MsLunch {

    private long c1 = 0;
    private long c2 = 0;

    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}

14

アクセスされるロックは、メソッドではなくオブジェクト上にあります。メソッド内でアクセスされる変数は関係ありません。

メソッドに「同期」を追加するということは、コードを実行するスレッドが処理を進める前にオブジェクトのロックを取得する必要があることを意味します。「静的同期」を追加することは、コードを実行するスレッドが続行する前にクラスオブジェクトのロックを取得する必要があることを意味します。または、次のようにコードをブロックでラップすることもできます。

public void addA() {
    synchronized(this) {
        a++;
    }
}

ロックを取得する必要があるオブジェクトを指定できるようにします。

含まれているオブジェクトのロックを回避する場合は、次のいずれかを選択できます。


7

オラクルのドキュメントリンクから

メソッドを同期させると、2つの効果があります。

まず、同じオブジェクト上で同期されたメソッドの2つの呼び出しをインターリーブすることはできません。1つのスレッドがオブジェクトの同期メソッドを実行しているとき、同じオブジェクトの同期メソッドを呼び出す他のすべてのスレッドは、最初のスレッドがオブジェクトで完了するまでブロックします(実行を中断します)。

第2に、同期メソッドが終了すると、同じオブジェクトの同期メソッドの以降の呼び出しと、前に発生する関係が自動的に確立されます。これにより、オブジェクトの状態の変更がすべてのスレッドに表示されることが保証されます

このドキュメントのページを見て、固有のロックとロックの動作を理解してください

これはあなたの質問に答えます:同じオブジェクトxで、同期されたメソッドの実行の1つが進行中のときに、x.addA()とx.addB()を同時に呼び出すことはできません。


4

同期されていないメソッドがあり、インスタンス変数にアクセスして変更している場合。あなたの例では:

 private int a;
 private int b;

他のスレッドが同じオブジェクトの同期メソッド内にある場合、任意の数のスレッドがこれらの非同期メソッドに同時にアクセスでき、インスタンス変数を変更できます。例えば:-

 public void changeState() {
      a++;
      b++;
    }

同期されていないメソッドがインスタンス変数にアクセスして変更するというシナリオを回避する必要があります。そうしないと、同期されたメソッドを使用する意味がありません。

以下のシナリオでは:-

class X {

        private int a;
        private int b;

        public synchronized void addA(){
            a++;
        }

        public synchronized void addB(){
            b++;
        }
     public void changeState() {
          a++;
          b++;
        }
    }

addAメソッドまたはaddBメソッドに含めることができるスレッドは1つだけですが、同時に、任意の数のスレッドがchangeStateメソッドに入ることができます。2つのスレッドが(オブジェクトレベルのロックのため)addAとaddBに同時に入ることはできませんが、同時に任意の数のスレッドがchangeStateに入ることができます。


3

次のようなことができます。この場合、「this」のロックの代わりに、aとbのロックを同期に使用しています。プリミティブ値にはロックがないため、intは使用できません。したがって、Integerを使用します。

class x{
   private Integer a;
   private Integer b;
   public void addA(){
      synchronized(a) {
         a++;
      }
   }
   public synchronized void addB(){
      synchronized(b) {
         b++;
      }
   }
}

3

はい、同期方法が適用されるので、それは他の方法をブロックしますWHOLE指摘したように、クラスオブジェクト....とにかく、それは他のスレッドの実行をブロックしますONLYそれが終了したときので、それが入るどんな方法ADDAまたはADDBに合計を行いながら、 ... 1つのスレッドはオブジェクトを解放し、もう1つのスレッドは他のメソッドにアクセスするなど、完全に機能します。

「同期」は、特定のコード実行中に他のスレッドが別のスレッドにアクセスするのをブロックするために正確に作成されていることを意味します。最後に、このコードは問題なく機能します。

最後に、一意の変数「a」やその他の名前だけでなく、「a」および「b」の変数がある場合、このメソッドを同期する必要はないため、他の変数(他のメモリ)にアクセスしても完全に安全です。ロケーション)。

class X {

private int a;
private int b;

public void addA(){
    a++;
}

public void addB(){
    b++;
}}

同様に動作します


2

この例(きれいな例ではありません)は、ロックメカニズムについてより多くの洞察を提供します。場合incrementAがされ、同期、およびincrementBがされて同期していない、そしてincrementBはできるだけ早く実行されますが、場合incrementBはまた、され、同期、それはのための「待機」にありincrementA前に、仕上がりにincrementBはその仕事をすることができます。

両方の方法は、単一のインスタンスに呼ばれている-の目的は、この例ではそれは:ジョブ、および「競合」スレッドでありaThreadメイン

「で試し同期に」incrementB、それなしで、あなたは異なるresults.If表示されますincrementBが「は同期だけでなく、それがために待機しなければならないとして、」incrementA仕上がりに()。各バリアントを数回実行します。

class LockTest implements Runnable {
    int a = 0;
    int b = 0;

    public synchronized void incrementA() {
        for (int i = 0; i < 100; i++) {
            this.a++;
            System.out.println("Thread: " + Thread.currentThread().getName() + "; a: " + this.a);
        }
    }

    // Try with 'synchronized' and without it and you will see different results
    // if incrementB is 'synchronized' as well then it has to wait for incrementA() to finish

    // public void incrementB() {
    public synchronized void incrementB() {
        this.b++;
        System.out.println("*************** incrementB ********************");
        System.out.println("Thread: " + Thread.currentThread().getName() + "; b: " + this.b);
        System.out.println("*************** incrementB ********************");
    }

    @Override
    public void run() {
        incrementA();
        System.out.println("************ incrementA completed *************");
    }
}

class LockTestMain {
    public static void main(String[] args) throws InterruptedException {
        LockTest job = new LockTest();
        Thread aThread = new Thread(job);
        aThread.setName("aThread");
        aThread.start();
        Thread.sleep(1);
        System.out.println("*************** 'main' calling metod: incrementB **********************");
        job.incrementB();
    }
}

1

Java同期では、スレッドが同期メソッドに入る場合、そのスレッドが使用している1つの同期メソッドだけでなく、そのオブジェクトのすべての同期メソッドのロックを取得します。したがって、addA()を実行するスレッドは、addA()とaddB()の両方が同期されるときにロックを取得します。したがって、同じオブジェクトを持つ他のスレッドはaddB()を実行できません。


0

Integerからintへのボクシングとオートボクシング、およびその逆はJVMに依存しているため、これは機能しない可能性があり、2つの異なる数値が-128から127の間の場合、同じアドレスにハッシュされる可能性が高くなります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.