Blocco metodo sincronizzato Java su oggetto o metodo?


191

Se ho 2 metodi sincronizzati nella stessa classe, ma ognuno accede a variabili diverse, 2 thread possono accedere a quei 2 metodi contemporaneamente? Il blocco si verifica sull'oggetto o diventa specifico come le variabili all'interno del metodo sincronizzato?

Esempio:

class X {

    private int a;
    private int b;

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

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

}

2 thread possono accedere alla stessa istanza dell'esecuzione di classe X x.addA() e x.addB()allo stesso tempo?

Risposte:


199

Se dichiari il metodo sincronizzato (come stai facendo digitando public synchronized void addA()) ti sincronizzi sull'intero oggetto, quindi due thread che accedono a una variabile diversa dallo stesso oggetto si bloccheranno comunque.

Se vuoi sincronizzare solo una variabile alla volta, quindi due thread non si bloccano l'un l'altro mentre accedono a variabili diverse, devi sincronizzarle separatamente in synchronized ()blocchi. Se ae bfossi riferimenti ad oggetti useresti:

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

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

Ma dal momento che sono primitivi non puoi farlo.

Ti suggerirei invece di utilizzare AtomicInteger :

import java.util.concurrent.atomic.AtomicInteger;

class X {

    AtomicInteger a;
    AtomicInteger b;

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

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

181
Se sincronizzi il metodo, blocchi l'intero oggetto, quindi due thread che accedono a una variabile diversa dallo stesso oggetto si bloccheranno comunque. È un po 'fuorviante. La sincronizzazione sul metodo è funzionalmente equivalente ad avere un synchronized (this)blocco attorno al corpo del metodo. L'oggetto "questo" non viene bloccato, piuttosto l'oggetto "questo" viene utilizzato come mutex e al corpo viene impedito di eseguire contemporaneamente altre sezioni di codice sincronizzate su "questo". Non ha alcun effetto su altri campi / metodi di "this" che non sono sincronizzati.
Mark Peters,

13
Sì, è davvero fuorviante. Per un vero esempio - Guarda questo - stackoverflow.com/questions/14447095/… - Riepilogo: il blocco è solo a livello di metodo sincronizzato e le variabili di istanza dell'oggetto sono accessibili da altri thread
mac

5
Il primo esempio è sostanzialmente rotto. Se ae se bfossero oggetti, ad es Integer., Ti stavi sincronizzando su istanze che stai sostituendo con oggetti diversi durante l'applicazione ++dell'operatore.
Holger,

correggi la tua risposta e inizializza AtomicInteger: AtomicInteger a = new AtomicInteger (0);
Mehdi,

Forse questo anwser dovrebbe essere aggiornato con l'spiegato in questo altro sulla sincronizzazione sull'oggetto stesso: stackoverflow.com/a/10324280/1099452
lucasvc

71

Sincronizzato sulla dichiarazione del metodo è lo zucchero sintattico per questo:

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

Su un metodo statico è lo zucchero sintattico per questo:

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

Penso che se i progettisti Java avessero saputo allora cosa si intende ora sulla sincronizzazione, non avrebbero aggiunto lo zucchero sintattico, poiché il più delle volte porta a cattive implementazioni della concorrenza.


3
Non vero. il metodo sincronizzato genera un bytecode diverso da quello sincronizzato (oggetto). Sebbene equivalente in termini di funzionalità, è molto più che zucchero sintattico.
Steve Kuo,

10
Non penso che lo "zucchero sintattico" sia strettamente definito come equivalente di byte-code. Il punto è che è funzionalmente equivalente.
Yishai,

1
Se i progettisti Java avessero saputo ciò che era già noto sui monitor, avrebbero / avrebbero dovuto farlo diversamente, invece di emulare sostanzialmente le viscere di Unix. Hans Bren afferma che "chiaramente ho lavorato invano" quando ha visto i primitivi della concorrenza Java .
Marchese di Lorne,

Questo è vero. L'esempio fornito da OP sembrerebbe bloccare ogni metodo ma in realtà tutti si bloccano sullo stesso oggetto. Sintassi molto ingannevole. Dopo aver usato Java per oltre 10 anni non lo sapevo. Quindi eviterei i metodi sincronizzati per questo motivo. Ho sempre pensato che fosse stato creato un oggetto invisibile per ogni metodo che era stato definito con sincronizzato.
Peter Quiring,

21

Da "I tutorial Java ™" sui metodi sincronizzati :

In primo luogo, non è possibile interlacciare due invocazioni di metodi sincronizzati sullo stesso oggetto . Quando un thread esegue un metodo sincronizzato per un oggetto, tutti gli altri thread che invocano metodi sincronizzati per lo stesso blocco di oggetti (sospensione dell'esecuzione) fino a quando il primo thread non viene eseguito con l'oggetto.

Da "I tutorial Java ™" su blocchi sincronizzati :

Le istruzioni sincronizzate sono utili anche per migliorare la concorrenza con una sincronizzazione dettagliata. Supponiamo, ad esempio, che la classe MsLunch abbia due campi di istanza, c1 e c2, che non vengono mai usati insieme. Tutti gli aggiornamenti di questi campi devono essere sincronizzati, ma non vi è alcun motivo per impedire che un aggiornamento di c1 venga intercalato con un aggiornamento di c2 e, in tal modo, si riduce la concorrenza creando blocchi inutili. Invece di utilizzare metodi sincronizzati o utilizzare in altro modo il blocco associato, creiamo due oggetti esclusivamente per fornire blocchi.

(Enfasi mia)

Supponi di averne 2 variabili non interleaving . Quindi vuoi accedere a ciascuno da thread diversi contemporaneamente. È necessario definire il blocco non sulla classe oggetto stessa, ma sulla classe Oggetto come di seguito (esempio dal secondo collegamento 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

Il blocco a cui si accede si trova sull'oggetto, non sul metodo. Quali variabili sono accessibili all'interno del metodo è irrilevante.

L'aggiunta di "sincronizzato" al metodo significa che il thread che esegue il codice deve acquisire il blocco sull'oggetto prima di procedere. L'aggiunta di "sincronizzazione statica" significa che il thread che esegue il codice deve acquisire il blocco sull'oggetto classe prima di procedere. In alternativa puoi avvolgere il codice in un blocco come questo:

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

in modo da poter specificare l'oggetto il cui blocco deve essere acquisito.

Se si desidera evitare il blocco sull'oggetto contenente, è possibile scegliere tra:


7

Dal collegamento alla documentazione di Oracle

Rendere sincronizzati i metodi ha due effetti:

In primo luogo, non è possibile interlacciare due invocazioni di metodi sincronizzati sullo stesso oggetto. Quando un thread esegue un metodo sincronizzato per un oggetto, tutti gli altri thread che invocano metodi sincronizzati per lo stesso blocco di oggetti (sospensione dell'esecuzione) fino a quando il primo thread non viene eseguito con l'oggetto.

In secondo luogo, quando esce un metodo sincronizzato, stabilisce automaticamente una relazione prima dell'evocazione successiva di un metodo sincronizzato per lo stesso oggetto. Ciò garantisce che le modifiche allo stato dell'oggetto siano visibili a tutti i thread

Dai un'occhiata a questa pagina di documentazione per comprendere i blocchi intrinseci e il comportamento dei blocchi.

Questo risponderà alla tua domanda: sullo stesso oggetto x, non puoi chiamare x.addA () e x.addB () contemporaneamente quando è in corso l'esecuzione di uno dei metodi sincronizzati.


4

Se hai alcuni metodi che non sono sincronizzati e stanno accedendo e modificando le variabili dell'istanza. Nel tuo esempio:

 private int a;
 private int b;

qualsiasi numero di thread può accedere a questi metodi non sincronizzati contemporaneamente quando altri thread si trovano nel metodo sincronizzato dello stesso oggetto e possono apportare modifiche alle variabili di istanza. Ad esempio: -

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

È necessario evitare lo scenario in cui i metodi non sincronizzati accedono alle variabili dell'istanza e la modificano, altrimenti non ha senso utilizzare i metodi sincronizzati.

Nello scenario seguente: -

class X {

        private int a;
        private int b;

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

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

Solo uno dei thread può essere nel metodo addA o addB ma allo stesso tempo un numero qualsiasi di thread può accedere al metodo changeState. Nessun due thread possono inserire addA e addB contemporaneamente (a causa del blocco a livello di oggetto) ma allo stesso tempo qualsiasi numero di thread può entrare in changeState.


3

Puoi fare qualcosa di simile al seguente. In questo caso si utilizza il blocco su aeb per sincronizzare anziché il blocco su "questo". Non possiamo usare int perché i valori primitivi non hanno blocchi, quindi usiamo Integer.

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

3

Sì, bloccherà l'altro metodo perché metodo sincronizzato applica al INTERO oggetto di classe come appuntito .... ma comunque bloccherà l'altra esecuzione filo SOLO durante l'esecuzione della somma in qualsiasi metodo o ADDA addB esso entra, perché quando terminare ... un thread libererà l'oggetto e l'altro thread accederà all'altro metodo e così via perfettamente funzionante.

Voglio dire che "sincronizzato" è fatto apposta per bloccare l'altro thread dall'accesso ad un altro durante l'esecuzione di un codice specifico. FINALMENTE QUESTO CODICE FUNZIONA FINE.

Come nota finale, se ci sono variabili 'a' e 'b', non solo una variabile univoca 'a' o qualsiasi altro nome, non è necessario sincronizzare questo metodo perché è perfettamente sicuro accedere ad altre var (altra memoria Posizione).

class X {

private int a;
private int b;

public void addA(){
    a++;
}

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

Funzionerà anche


2

Questo esempio (sebbene non carino) può fornire maggiori informazioni sul meccanismo di blocco. Se incrementA è sincronizzato e incrementB non è sincronizzato , allora incrementB verrà eseguito APPENA POSSIBILE, ma se incrementB è anche sincronizzato , deve "attendere" il completamento di incrementA , prima di incrementB possa fare il suo lavoro.

Entrambi i metodi sono chiamati su una singola istanza - oggetto, in questo esempio è: lavoro , e i thread 'concorrenti' sono aThread e main .

Prova con " sincronizzato " in incrementB e senza di esso e vedrai risultati diversi. Se incrementB è anche " sincronizzato ", allora deve attendere il completamento di incrementA (). Esegui più volte ogni variante.

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

Nella sincronizzazione java, se un thread vuole entrare nel metodo di sincronizzazione acquisirà il blocco su tutti i metodi sincronizzati di quell'oggetto, non solo su un metodo sincronizzato che il thread sta usando. Quindi un thread che esegue addA () acquisirà il blocco su addA () e addB () poiché entrambi sono sincronizzati, quindi altri thread con lo stesso oggetto non possono eseguire addB ().


0

Questo potrebbe non funzionare poiché il boxe e l'autoboxing da Integer a int e viceversa dipendono da JVM e c'è un'alta possibilità che due numeri diversi possano essere sottoposti a hash sullo stesso indirizzo se sono compresi tra -128 e 127.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.