Esiste un vantaggio nell'utilizzare un metodo sincronizzato anziché un blocco sincronizzato?


401

Qualcuno può dirmi il vantaggio del metodo sincronizzato rispetto al blocco sincronizzato con un esempio?




1
@cletus questa domanda è completamente diversa da stackoverflow.com/questions/442564/…
Yukio Fukuzawa,

Risposte:


431

Qualcuno può dirmi il vantaggio del metodo sincronizzato rispetto al blocco sincronizzato con un esempio? Grazie.

Non esiste un chiaro vantaggio dell'utilizzo del metodo sincronizzato sul blocco.

Forse l'unico (ma non lo definirei un vantaggio) è che non è necessario includere il riferimento all'oggetto this.

Metodo:

public synchronized void method() { // blocks "this" from here.... 
    ...
    ...
    ...
} // to here

Bloccare:

public void method() { 
    synchronized( this ) { // blocks "this" from here .... 
        ....
        ....
        ....
    }  // to here...
}

Vedere? Nessun vantaggio.

I blocchi non hanno vantaggi rispetto ai metodi, però, per lo più in termini di flessibilità, perché è possibile utilizzare un altro oggetto come blocco, mentre la sincronizzazione il metodo sarebbe bloccare l'intero oggetto.

Confrontare:

// locks the whole object
... 
private synchronized void someInputRelatedWork() {
    ... 
}
private synchronized void someOutputRelatedWork() {
    ... 
}

vs.

// Using specific locks
Object inputLock = new Object();
Object outputLock = new Object();

private void someInputRelatedWork() {
    synchronized(inputLock) { 
        ... 
    } 
}
private void someOutputRelatedWork() {
    synchronized(outputLock) { 
        ... 
    }
}

Inoltre, se il metodo aumenta, puoi comunque mantenere separata la sezione sincronizzata:

 private void method() {
     ... code here
     ... code here
     ... code here
    synchronized( lock ) { 
        ... very few lines of code here
    }
     ... code here
     ... code here
     ... code here
     ... code here
}

44
Un vantaggio per il consumatore dell'API è che l'uso della parola chiave sincronizzata nella dichiarazione del metodo dichiara esplicitamente che il metodo si sincronizza sull'istanza dell'oggetto ed è (presumibilmente) thread-safe.
Scrubbie

59
So che questa è una vecchia domanda, ma la sincronizzazione su "questo" è considerata in alcuni ambienti come un anti-schema. La conseguenza non intenzionale è che al di fuori della classe qualcuno può bloccare un riferimento a un oggetto uguale a "questo" e impedire ad altri thread di superare le barriere all'interno della classe creando potenzialmente una situazione di deadlock. Creazione di un "Oggetto finale privato = nuovo Oggetto ();" variabile puramente a scopo di blocco è la soluzione spesso utilizzata. Ecco un'altra domanda relativa direttamente a questo problema.
justin.hughey,

30
"considerando che la sincronizzazione del metodo bloccherebbe la classe completa." Questo non è corretto Non blocca la classe completa, ma l'istanza completa. Più oggetti della stessa classe mantengono tutto il proprio blocco. :) Saluta
codepleb,

4
Una cosa interessante di questo è che l'uso di un metodo sincronizzato farà sì che il bytecode generato abbia 1 istruzione in meno, poiché i metodi hanno un bit sincronizzato inserito nella loro firma. Poiché la lunghezza del bytecode è un fattore determinante per stabilire se un metodo viene allineato, spostare il blocco sulla firma del metodo potrebbe essere la differenza nella decisione. In teoria comunque. Non baserei una decisione di progettazione su una singola istruzione bytecode salvata, che sembra un'idea terribile. Ma comunque, è una differenza. =)
corsiKa

2
@corsiKa: risparmi più di un'istruzione. Un synchronizedblocco viene implementato utilizzando due istruzioni monitorentere monitorexit, oltre a un gestore di eccezioni, che assicura che monitorexitvenga chiamato anche in casi eccezionali. Questo è tutto salvato quando si utilizza un synchronizedmetodo.
Holger,

139

L'unica vera differenza è che un blocco sincronizzato può scegliere su quale oggetto sincronizzare. Un metodo sincronizzato può utilizzare solo 'this'(o l'istanza di classe corrispondente per un metodo di classe sincronizzato). Ad esempio, questi sono semanticamente equivalenti:

synchronized void foo() {
  ...
}

void foo() {
    synchronized (this) {
      ...
    }
}

Quest'ultimo è più flessibile poiché può competere per il blocco associato di qualsiasi oggetto, spesso una variabile membro. È anche più granulare perché potresti avere l'esecuzione simultanea di codice prima e dopo il blocco ma comunque all'interno del metodo. Naturalmente, è possibile utilizzare altrettanto facilmente un metodo sincronizzato rifattorizzando il codice simultaneo in metodi separati non sincronizzati. Usa qualunque cosa renda il codice più comprensibile.


Quest'ultimo può anche avere merito se non è necessario sincronizzare tutto il codice in foo ().
Evan,

1
Questo è vero, ma non quello che "Warrior" ha chiesto: "Il vantaggio del metodo sincronizzato" non esiste.
OscarRyz,

76

Metodo sincronizzato

Professionisti:

  • Il tuo IDE può indicare i metodi sincronizzati.
  • La sintassi è più compatta.
  • Forza la divisione dei blocchi sincronizzati in metodi separati.

Contro:

  • Sincronizza con questo e consente così anche agli estranei di sincronizzarsi con esso.
  • È più difficile spostare il codice all'esterno del blocco sincronizzato.

Blocco sincronizzato

Professionisti:

  • Consente di utilizzare una variabile privata per il blocco e quindi forzare il blocco a rimanere all'interno della classe.
  • I blocchi sincronizzati possono essere trovati cercando i riferimenti alla variabile.

Contro:

  • La sintassi è più complicata e rende il codice più difficile da leggere.

Personalmente preferisco usare metodi sincronizzati con classi focalizzate solo sulla cosa che necessita di sincronizzazione. Tale classe dovrebbe essere il più piccola possibile e quindi dovrebbe essere facile rivedere la sincronizzazione. Altri non dovrebbero preoccuparsi della sincronizzazione.


Quando dici "Resta all'interno della classe" intendi "Resta all'interno dell'oggetto " o mi sto perdendo qualcosa?
OldPeculier,

36

La differenza principale è che se si utilizza un blocco sincronizzato è possibile bloccare un oggetto diverso da questo, il che consente di essere molto più flessibile.

Supponiamo di avere una coda di messaggi e più produttori e consumatori di messaggi. Non vogliamo che i produttori interferiscano l'uno con l'altro, ma i consumatori dovrebbero essere in grado di recuperare i messaggi senza dover aspettare i produttori. Quindi creiamo solo un oggetto

Object writeLock = new Object();

E d'ora in poi ogni volta che un produttore vuole aggiungere un nuovo messaggio, ci limitiamo a bloccarlo:

synchronized(writeLock){
  // do something
}

Quindi i consumatori possono ancora leggere e i produttori saranno bloccati.


2
Il tuo esempio è limitato a letture non distruttive. Se la lettura rimuove il messaggio dalla coda, ciò non andrà a buon fine se viene eseguito nel momento in cui un produttore scrive nella coda.
ceving,

30

Metodo sincronizzato

I metodi sincronizzati hanno due effetti.
In primo luogo, 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.

Si noti che i costruttori non possono essere sincronizzati: l'uso della parola chiave sincronizzata con un costruttore è un errore di sintassi. Sincronizzare i costruttori non ha senso, perché solo il thread che crea un oggetto dovrebbe avervi accesso mentre viene costruito.

Dichiarazione sincronizzata

A differenza dei metodi sincronizzati, le istruzioni sincronizzate devono specificare l'oggetto che fornisce il blocco intrinseco: il più delle volte lo utilizzo per sincronizzare l'accesso a un elenco o una mappa ma non voglio bloccare l'accesso a tutti i metodi dell'oggetto.

D: Blocchi intrinseci e sincronizzazione La sincronizzazione si basa su un'entità interna nota come blocco intrinseco o blocco monitor. (Le specifiche API spesso si riferiscono a questa entità semplicemente come un "monitor".) I blocchi intrinsechi svolgono un ruolo in entrambi gli aspetti della sincronizzazione: imporre l'accesso esclusivo allo stato di un oggetto e stabilire relazioni che accadono prima che siano essenziali per la visibilità.

A ogni oggetto è associato un blocco intrinseco. Per convenzione, un thread che necessita di un accesso esclusivo e coerente ai campi di un oggetto deve acquisire il blocco intrinseco dell'oggetto prima di accedervi, quindi rilasciare il blocco intrinseco una volta terminato con essi. Si dice che un thread possiede il blocco intrinseco tra il momento in cui ha acquisito il blocco e rilasciato il blocco. Finché un thread possiede un blocco intrinseco, nessun altro thread può acquisire lo stesso blocco. L'altro thread si bloccherà quando tenta di acquisire il blocco.

package test;

public class SynchTest implements Runnable {  
    private int c = 0;

    public static void main(String[] args) {
        new SynchTest().test();
    }

    public void test() {
        // Create the object with the run() method
        Runnable runnable = new SynchTest();
        Runnable runnable2 = new SynchTest();
        // Create the thread supplying it with the runnable object
        Thread thread = new Thread(runnable,"thread-1");
        Thread thread2 = new Thread(runnable,"thread-2");
//      Here the key point is passing same object, if you pass runnable2 for thread2,
//      then its not applicable for synchronization test and that wont give expected
//      output Synchronization method means "it is not possible for two invocations
//      of synchronized methods on the same object to interleave"

        // Start the thread
        thread.start();
        thread2.start();
    }

    public synchronized  void increment() {
        System.out.println("Begin thread " + Thread.currentThread().getName());
        System.out.println(this.hashCode() + "Value of C = " + c);
//      If we uncomment this for synchronized block, then the result would be different
//      synchronized(this) {
            for (int i = 0; i < 9999999; i++) {
                c += i;
            }
//      }
        System.out.println("End thread " + Thread.currentThread().getName());
    }

//    public synchronized void decrement() {
//        System.out.println("Decrement " + Thread.currentThread().getName());
//    }

    public int value() {
        return c;
    }

    @Override
    public void run() {
        this.increment();
    }
}

Controllo incrociato di diverse uscite con metodo sincronizzato, blocco e senza sincronizzazione.


10
+1 per essere l'unico finora a menzionare che i costruttori non possono essere sincronizzati . Cioè, in un costruttore hai davvero una sola opzione: blocchi sincronizzati.
ef2011,

Ho testato il tuo codice come indicato ma C è sempre 0, quindi -2024260031 e l'unica cosa che lo cambia il codice hash. Quale comportamento dovrebbe essere visto?
Justin Johnson,

Dovresti aver citato di seguito gli articoli da cui è stato fornito il contenuto: docs.oracle.com/javase/tutorial/essential/concurrency/… e docs.oracle.com/javase/tutorial/essential/concurrency/…
Ravindra babu

29

Nota: metodi e blocchi sincronizzati statici funzionano sull'oggetto Class.

public class MyClass {
   // locks MyClass.class
   public static synchronized void foo() {
// do something
   }

   // similar
   public static void foo() {
      synchronized(MyClass.class) {
// do something
      }
   }
}

18

Quando il compilatore java converte il codice sorgente in codice byte, gestisce i metodi sincronizzati e i blocchi sincronizzati in modo molto diverso.

Quando la JVM esegue un metodo sincronizzato, il thread in esecuzione identifica che la struttura method_info del metodo ha il flag ACC_SYNCHRONIZED impostato, quindi acquisisce automaticamente il blocco dell'oggetto, chiama il metodo e rilascia il blocco. Se si verifica un'eccezione, il thread rilascia automaticamente il blocco.

La sincronizzazione di un blocco di metodo, d'altro canto, ignora il supporto integrato della JVM per l'acquisizione del blocco di un oggetto e della gestione delle eccezioni e richiede che la funzionalità sia esplicitamente scritta nel codice byte. Se leggi il codice byte per un metodo con un blocco sincronizzato, vedrai più di una dozzina di operazioni aggiuntive per gestire questa funzionalità.

Questo mostra le chiamate per generare sia un metodo sincronizzato che un blocco sincronizzato:

public class SynchronizationExample {
    private int i;

    public synchronized int synchronizedMethodGet() {
        return i;
    }

    public int synchronizedBlockGet() {
        synchronized( this ) {
            return i;
        }
    }
}

Il synchronizedMethodGet()metodo genera il seguente codice byte:

0:  aload_0
1:  getfield
2:  nop
3:  iconst_m1
4:  ireturn

Ed ecco il codice byte dal synchronizedBlockGet()metodo:

0:  aload_0
1:  dup
2:  astore_1
3:  monitorenter
4:  aload_0
5:  getfield
6:  nop
7:  iconst_m1
8:  aload_1
9:  monitorexit
10: ireturn
11: astore_2
12: aload_1
13: monitorexit
14: aload_2
15: athrow

Una differenza significativa tra il metodo sincronizzato e il blocco è che, il blocco sincronizzato generalmente riduce l'ambito del blocco. Poiché l'ambito del blocco è inversamente proporzionale alle prestazioni, è sempre meglio bloccare solo una sezione critica del codice. Uno dei migliori esempi di utilizzo del blocco sincronizzato è il doppio controllo bloccato nel modello Singleton dove invece di bloccare l'intero getInstance()metodo blocciamo solo la sezione critica del codice che viene utilizzata per creare l'istanza Singleton. Ciò migliora drasticamente le prestazioni perché il blocco è richiesto solo una o due volte.

Durante l'utilizzo di metodi sincronizzati, è necessario prestare particolare attenzione se si mescolano metodi sincronizzati statici e non sincronizzati.


1
Se osserviamo il metodo sincronizzato bytecode, il bytecode è più compatto e semplice, quindi perché non è più veloce di quel blocco sincronizzato?
EatSleepCode

@eatSleepCode Si noti che questo è bytecode che viene ulteriormente "compilato" da JVM. La JVM aggiungerà il necessario monitorentere monitorexitprima di eseguire il codice.
Philip Couling,

12

Molto spesso lo uso per sincronizzare l'accesso a un elenco o una mappa ma non voglio bloccare l'accesso a tutti i metodi dell'oggetto.

Nel codice seguente un thread che modifica l'elenco non bloccherà l'attesa di un thread che sta modificando la mappa. Se i metodi fossero sincronizzati sull'oggetto, ciascun metodo dovrebbe attendere anche se le modifiche che stanno apportando non sarebbero in conflitto.

private List<Foo> myList = new ArrayList<Foo>();
private Map<String,Bar) myMap = new HashMap<String,Bar>();

public void put( String s, Bar b ) {
  synchronized( myMap ) {
    myMap.put( s,b );
    // then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public void hasKey( String s, ) {
  synchronized( myMap ) {
    myMap.hasKey( s );
  }
}

public void add( Foo f ) {
  synchronized( myList ) {
    myList.add( f );
// then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public Thing getMedianFoo() {
  Foo med = null;
  synchronized( myList ) {
    Collections.sort(myList);
    med = myList.get(myList.size()/2); 
  }
  return med;
}

7

Con i blocchi sincronizzati, puoi avere più sincronizzatori, in modo che più cose simultanee ma non in conflitto possano continuare contemporaneamente.


6

I metodi sincronizzati possono essere controllati utilizzando l'API di reflection. Ciò può essere utile per testare alcuni contratti, ad esempio tutti i metodi nel modello sono sincronizzati .

Il frammento seguente stampa tutti i metodi sincronizzati di Hashtable:

for (Method m : Hashtable.class.getMethods()) {
        if (Modifier.isSynchronized(m.getModifiers())) {
            System.out.println(m);
        }
}

5

Nota importante sull'uso del blocco sincronizzato: fai attenzione a cosa usi come oggetto lock!

Lo snippet di codice di user2277816 sopra illustra questo punto in quanto un riferimento a un valore letterale di stringa viene utilizzato come oggetto di blocco. Comprendi che i valori letterali delle stringhe vengono automaticamente internati in Java e dovresti iniziare a vedere il problema: ogni pezzo di codice che si sincronizza sul "blocco" letterale, condivide lo stesso blocco! Questo può facilmente portare a deadlock con pezzi di codice completamente non correlati.

Non è solo con gli oggetti String che devi stare attento. Anche le primitive inscatolate sono un pericolo, poiché l'autoboxing e i metodi valueOf possono riutilizzare gli stessi oggetti, a seconda del valore.

Per ulteriori informazioni, consultare: https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reused


5

Spesso l'utilizzo di un blocco a livello di metodo è troppo scortese. Perché bloccare un pezzo di codice che non accede a nessuna risorsa condivisa bloccando un intero metodo. Poiché ogni oggetto ha un blocco, è possibile creare oggetti fittizi per implementare la sincronizzazione a livello di blocco. Il livello di blocco è più efficiente perché non blocca l'intero metodo.

Ecco qualche esempio

Livello del metodo

class MethodLevel {

  //shared among threads
SharedResource x, y ;

public void synchronized method1() {
   //multiple threads can't access
}
public void synchronized method2() {
  //multiple threads can't access
}

 public void method3() {
  //not synchronized
  //multiple threads can access
 }
}

Livello di blocco

class BlockLevel {
  //shared among threads
  SharedResource x, y ;

  //dummy objects for locking
  Object xLock = new Object();
  Object yLock = new Object();

    public void method1() {
     synchronized(xLock){
    //access x here. thread safe
    }

    //do something here but don't use SharedResource x, y
    // because will not be thread-safe
     synchronized(xLock) {
       synchronized(yLock) {
      //access x,y here. thread safe
      }
     }

     //do something here but don't use SharedResource x, y
     //because will not be thread-safe
    }//end of method1
 }

[Modificare]

Per Collectioncome Vectore Hashtablesono sincronizzati quando ArrayListo HashMapnon lo sono e devi impostare una parola chiave sincronizzata o invocare il metodo sincronizzato Collezioni:

Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map
List myList = Collections.synchronizedList (myList); // single lock for the entire list

5

L'unica differenza: i blocchi sincronizzati consentono il blocco granulare a differenza del metodo sincronizzato

Fondamentalmente synchronizedblocco o metodi sono stati usati per scrivere il codice thread-safe evitando errori di incoerenza della memoria.

Questa domanda è molto antica e molte cose sono state cambiate negli ultimi 7 anni. Sono stati introdotti nuovi costrutti di programmazione per la sicurezza del thread.

Puoi ottenere la sicurezza dei thread utilizzando l'API di concorrenza avanzata anziché i synchroniedblocchi. Questa pagina di documentazione fornisce buoni costrutti di programmazione per ottenere la sicurezza del thread.

Lock Objects supporta i modi di bloccare che semplificano molte applicazioni simultanee.

I dirigenti definiscono un'API di alto livello per l'avvio e la gestione dei thread. Le implementazioni di Executor fornite da java.util.concurrent forniscono la gestione del pool di thread adatta per applicazioni su larga scala.

Le raccolte simultanee semplificano la gestione di grandi raccolte di dati e possono ridurre notevolmente la necessità di sincronizzazione.

Le variabili atomiche hanno funzionalità che riducono al minimo la sincronizzazione e aiutano a evitare errori di coerenza della memoria.

ThreadLocalRandom (in JDK 7) fornisce una generazione efficiente di numeri pseudocasuali da più thread.

Una migliore sostituzione per sincronizzato è ReentrantLock , che utilizza l' LockAPI

Un blocco di esclusione reciproca rientrante con lo stesso comportamento di base e semantica del blocco di monitoraggio implicito a cui si accede utilizzando metodi e istruzioni sincronizzati, ma con funzionalità estese.

Esempio con blocchi:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

Consultare anche i pacchetti java.util.concurrent e java.util.concurrent.atomic per altri costrutti di programmazione.

Fare riferimento anche a questa domanda correlata:

Sincronizzazione vs blocco


4

Il metodo sincronizzato viene utilizzato per bloccare tutti gli oggetti Il blocco sincronizzato viene utilizzato per bloccare un oggetto specifico


3

In generale, questi sono per lo più diversi dall'essere espliciti sul monitor dell'oggetto utilizzato rispetto all'implicito oggetto. Un aspetto negativo dei metodi sincronizzati che a volte penso sia trascurato è che nell'usare il riferimento "this" per la sincronizzazione su di te si lascia aperta la possibilità che gli oggetti esterni si blocchino sullo stesso oggetto. Questo può essere un bug molto sottile se ci si imbatte. La sincronizzazione su un oggetto esplicito interno o su un altro campo esistente può evitare questo problema, incapsulando completamente la sincronizzazione.


2

Come già detto qui, il blocco sincronizzato può utilizzare la variabile definita dall'utente come oggetto di blocco, quando la funzione sincronizzata utilizza solo "questo". E ovviamente puoi manipolare aree della tua funzione che dovrebbero essere sincronizzate. Ma tutti dicono che nessuna differenza tra funzione sincronizzata e blocco che copre l'intera funzione usando "questo" come oggetto di blocco. Ciò non è vero, la differenza è nel codice byte che verrà generato in entrambe le situazioni. In caso di utilizzo del blocco sincronizzato dovrebbe essere allocata la variabile locale che contiene riferimenti a "this". E di conseguenza avremo dimensioni un po 'più grandi per la funzione (non rilevante se hai solo un numero limitato di funzioni).

Una spiegazione più dettagliata della differenza è possibile trovare qui: http://www.artima.com/insidejvm/ed2/threadsynchP.html


2

Nel caso di metodi sincronizzati, il blocco verrà acquisito su un oggetto. Ma se vai con il blocco sincronizzato hai un'opzione per specificare un oggetto su cui verrà acquisito il blocco.

Esempio :

    Class Example {
    String test = "abc";
    // lock will be acquired on String  test object.
    synchronized (test) {
        // do something
    }

   lock will be acquired on Example Object
   public synchronized void testMethod() {
     // do some thing
   } 

   }

2

So che questa è una vecchia domanda, ma con la mia rapida lettura delle risposte qui, non ho davvero visto nessuno menzionare che a volte un synchronizedmetodo potrebbe essere il blocco sbagliato .
Da Java Concurrency In Practice (pag. 72):

public class ListHelper<E> {
  public List<E> list = Collections.syncrhonizedList(new ArrayList<>());
...

public syncrhonized boolean putIfAbsent(E x) {
 boolean absent = !list.contains(x);
if(absent) {
 list.add(x);
}
return absent;
}

Il codice precedente ha l' aspetto di essere thread-safe. Tuttavia, in realtà non lo è. In questo caso il blocco si ottiene sull'istanza della classe. Tuttavia, è possibile che l' elenco venga modificato da un altro thread che non utilizza quel metodo. L'approccio corretto sarebbe usare

public boolean putIfAbsent(E x) {
 synchronized(list) {
  boolean absent = !list.contains(x);
  if(absent) {
    list.add(x);
  }
  return absent;
}
}

Il codice precedente bloccherebbe tutti i thread che tentano di modificare l' elenco dalla modifica dell'elenco fino al completamento del blocco sincronizzato.


leggendo questo libro al momento ... mi chiedo se quell'elenco fosse privato anziché pubblico e avesse solo il metodo putIfAbsent, sincronizzato (questo) sarebbe sufficiente giusto? il problema è che l'elenco può essere modificato anche al di fuori di ListHelper?
dtc,

Sì, se l'elenco era privato e non trapelava in nessun altro posto della classe, allora sarebbe sufficiente, purché tu abbia contrassegnato tutti gli altri metodi della classe che modificano anche l'elenco come sincronizzato. Tuttavia, il blocco dell'intero metodo anziché solo quello Listpuò causare problemi di prestazioni se esiste un registro di codice che non deve necessariamente essere sincronizzato
aarbor,

questo ha senso. grazie mille per aver risposto! ho trovato il libro abbastanza utile per espandere le mie conoscenze e come affrontare il multithreading, ma mi ha anche introdotto un nuovo mondo di confusione
dtc

2

In pratica, il vantaggio dei metodi sincronizzati rispetto ai blocchi sincronizzati è che sono più resistenti agli idioti; poiché non puoi scegliere un oggetto arbitrario da bloccare, non puoi usare in modo improprio la sintassi del metodo sincronizzato per fare cose stupide come bloccare letteralmente una stringa o bloccare il contenuto di un campo mutabile che viene modificato da sotto i thread.

D'altra parte, con i metodi sincronizzati non è possibile proteggere il blocco dall'acquisizione da parte di alcun thread in grado di ottenere un riferimento all'oggetto.

Quindi usare la sincronizzazione come modificatore sui metodi è meglio per proteggere i tuoi cow-orker da farsi male, mentre usare i blocchi sincronizzati in combinazione con oggetti di blocco finali privati ​​è meglio per proteggere il tuo codice dai cow-orker.


1

Da un riepilogo delle specifiche Java: http://www.cs.cornell.edu/andru/javaspec/17.doc.html

L'istruzione sincronizzata (§14.17) calcola un riferimento a un oggetto; tenta quindi di eseguire un'azione di blocco su quell'oggetto e non procede ulteriormente fino al completamento dell'azione di blocco. ...

Un metodo sincronizzato (§8.4.3.5) esegue automaticamente un'azione di blocco quando viene invocato; il suo corpo non viene eseguito fino a quando l'azione di blocco non è stata completata con successo. Se il metodo è un metodo di istanza , blocca il blocco associato all'istanza per la quale è stato invocato (ovvero, l'oggetto che sarà noto come questo durante l'esecuzione del corpo del metodo). Se il metodo è statico , blocca il blocco associato all'oggetto Class che rappresenta la classe in cui è definito il metodo. ...

Sulla base di queste descrizioni, direi che la maggior parte delle risposte precedenti sono corrette e che un metodo sincronizzato potrebbe essere particolarmente utile per i metodi statici, dove altrimenti dovresti capire come ottenere l'oggetto "Class che rappresenta la classe in cui era il metodo definito."

Modifica: inizialmente pensavo fossero citazioni delle specifiche Java attuali. Chiarito che questa pagina è solo un sommario / spiegazione delle specifiche


1

TLDR; Né utilizzare il synchronizedmodificatore né l' synchronized(this){...}espressione ma synchronized(myLock){...}dove si myLocktrova un campo dell'istanza finale che contiene un oggetto privato.


La differenza tra l'uso del synchronizedmodificatore nella dichiarazione del metodo e l' synchronized(..){ }espressione nel corpo del metodo sono queste:

  • Il synchronized modificatore specificato nella firma del metodo
    1. è visibile nel JavaDoc generato,
    2. è programmaticamente determinabile tramite la riflessione durante il test del modificatore di un metodo per Modificatore .
    3. richiede meno battitura e rientro rispetto a synchronized(this) { .... } , e
    4. (a seconda dell'IDE) è visibile nella struttura della classe e nel completamento del codice,
    5. usa l' thisoggetto come blocco quando dichiarato su un metodo non statico o la classe che lo racchiude quando dichiarato su un metodo statico.
  • Il synchronized(...){...} espressione ti consente
    1. sincronizzare solo l'esecuzione di parti del corpo di un metodo,
    2. da usare all'interno di un costruttore o di un ( statico blocco di inizializzazione ),
    3. per scegliere l'oggetto di blocco che controlla l'accesso sincronizzato.

Tuttavia, l'utilizzo del synchronizedmodificatore o synchronized(...) {...}con thiscome oggetto lock (come in synchronized(this) {...}) presenta lo stesso svantaggio. Entrambi usano la propria istanza come oggetto di blocco per la sincronizzazione. Questo è pericoloso perché non solo l'oggetto stesso ma qualsiasi altro oggetto / codice esterno che contiene un riferimento a quell'oggetto può anche usarlo come blocco di sincronizzazione con effetti collaterali potenzialmente gravi (degrado delle prestazioni e deadlock ).

Pertanto, la migliore pratica non è utilizzare né il synchronizedmodificatore né l' synchronized(...)espressione insieme thiscome oggetto lock ma un oggetto lock privato di questo oggetto. Per esempio:

public class MyService {
    private final lock = new Object();

    public void doThis() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }

    public void doThat() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }
}

È inoltre possibile utilizzare più oggetti di blocco, ma è necessario prestare particolare attenzione per garantire che ciò non comporti deadlock quando utilizzato nidificato.

public class MyService {
    private final lock1 = new Object();
    private final lock2 = new Object();

    public void doThis() {
       synchronized(lock1) {
          synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThat() and doMore().
          }
    }

    public void doThat() {
       synchronized(lock1) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doMore() may execute concurrently
        }
    }

    public void doMore() {
       synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doThat() may execute concurrently
        }
    }
}

1

Suppongo che questa domanda riguardi la differenza tra Thread Safe Singleton e l' inizializzazione Lazy con doppio controllo di blocco . Mi riferisco sempre a questo articolo quando devo implementare alcuni singleton specifici.

Bene, questo è un thread sicuro Singleton :

// Java program to create Thread Safe 
// Singleton class 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

 //synchronized method to control simultaneous access 
  synchronized public static GFG getInstance()  
  { 
    if (instance == null)  
    { 
      // if instance is null, initialize 
      instance = new GFG(); 
    } 
    return instance; 
  } 
} 

Professionisti:

  1. L'inizializzazione lenta è possibile.

  2. È thread-safe.

Contro:

  1. Il metodo getInstance () è sincronizzato, quindi causa prestazioni lente poiché più thread non possono accedervi contemporaneamente.

Questa è un'inizializzazione Lazy con blocco doppio controllo :

// Java code to explain double check locking 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

  public static GFG getInstance() 
  { 
    if (instance == null)  
    { 
      //synchronized block to remove overhead 
      synchronized (GFG.class) 
      { 
        if(instance==null) 
        { 
          // if instance is null, initialize 
          instance = new GFG(); 
        } 

      } 
    } 
    return instance; 
  } 
} 

Professionisti:

  1. L'inizializzazione lenta è possibile.

  2. È anche sicuro per i thread.

  3. Le prestazioni ridotte a causa della parola chiave sincronizzata vengono superate.

Contro:

  1. La prima volta, può influire sulle prestazioni.

  2. Come contro. del metodo di blocco a doppio controllo è sopportabile, quindi può essere utilizzato per applicazioni multi-thread ad alte prestazioni.

Si prega di fare riferimento a questo articolo per maggiori dettagli:

https://www.geeksforgeeks.org/java-singleton-design-pattern-practices-examples/


-3

Sincronizzazione con i thread. 1) MAI usare sincronizzato (questo) in un thread che non funziona. La sincronizzazione con (questo) utilizza il thread corrente come oggetto thread di blocco. Poiché ogni thread è indipendente dagli altri thread, NON esiste alcun coordinamento della sincronizzazione. 2) Test di codice mostrano che in Java 1.6 su un Mac la sincronizzazione del metodo non funziona. 3) sincronizzato (lockObj) dove lockObj è un oggetto condiviso comune di tutti i thread che si sincronizzano su di esso funzionerà. 4) ReenterantLock.lock () e .unlock () funzionano. Vedi tutorial Java per questo.

Il codice seguente mostra questi punti. Contiene anche il vettore thread-safe che verrebbe sostituito da ArrayList, per mostrare che molti thread che si aggiungono a un Vector non perdono alcuna informazione, mentre lo stesso con un ArrayList può perdere informazioni. 0) Il codice corrente mostra la perdita di informazioni a causa delle condizioni di gara A) Commenta la linea A attualmente etichettata, e decommenta la linea A sopra di essa, quindi esegui, il metodo perde i dati ma non dovrebbe. B) Invertire il passaggio A, decommentare B e // end block}. Quindi esegui per vedere i risultati senza perdita di dati. C) Commenta B, decommenta C. Esegui, vedi la sincronizzazione su (questo) perde i dati, come previsto. Non ho tempo per completare tutte le varianti, spero che questo aiuti. Se la sincronizzazione è attiva (presente) o la sincronizzazione del metodo funziona, indicare quale versione di Java e OS è stata testata. Grazie.

import java.util.*;

/** RaceCondition - Shows that when multiple threads compete for resources 
     thread one may grab the resource expecting to update a particular 
     area but is removed from the CPU before finishing.  Thread one still 
     points to that resource.  Then thread two grabs that resource and 
     completes the update.  Then thread one gets to complete the update, 
     which over writes thread two's work.
     DEMO:  1) Run as is - see missing counts from race condition, Run severa times, values change  
            2) Uncomment "synchronized(countLock){ }" - see counts work
            Synchronized creates a lock on that block of code, no other threads can 
            execute code within a block that another thread has a lock.
        3) Comment ArrayList, unComment Vector - See no loss in collection
            Vectors work like ArrayList, but Vectors are "Thread Safe"
         May use this code as long as attribution to the author remains intact.
     /mf
*/ 

public class RaceCondition {
    private ArrayList<Integer> raceList = new ArrayList<Integer>(); // simple add(#)
//  private Vector<Integer> raceList = new Vector<Integer>(); // simple add(#)

    private String countLock="lock";    // Object use for locking the raceCount
    private int raceCount = 0;        // simple add 1 to this counter
    private int MAX = 10000;        // Do this 10,000 times
    private int NUM_THREADS = 100;    // Create 100 threads

    public static void main(String [] args) {
    new RaceCondition();
    }

    public RaceCondition() {
    ArrayList<Thread> arT = new ArrayList<Thread>();

    // Create thread objects, add them to an array list
    for( int i=0; i<NUM_THREADS; i++){
        Thread rt = new RaceThread( ); // i );
        arT.add( rt );
    }

    // Start all object at once.
    for( Thread rt : arT ){
        rt.start();
    }

    // Wait for all threads to finish before we can print totals created by threads
    for( int i=0; i<NUM_THREADS; i++){
        try { arT.get(i).join(); }
        catch( InterruptedException ie ) { System.out.println("Interrupted thread "+i); }
    }

    // All threads finished, print the summary information.
    // (Try to print this informaiton without the join loop above)
    System.out.printf("\nRace condition, should have %,d. Really have %,d in array, and count of %,d.\n",
                MAX*NUM_THREADS, raceList.size(), raceCount );
    System.out.printf("Array lost %,d. Count lost %,d\n",
             MAX*NUM_THREADS-raceList.size(), MAX*NUM_THREADS-raceCount );
    }   // end RaceCondition constructor



    class RaceThread extends Thread {
    public void run() {
        for ( int i=0; i<MAX; i++){
        try {
            update( i );        
        }    // These  catches show when one thread steps on another's values
        catch( ArrayIndexOutOfBoundsException ai ){ System.out.print("A"); }
        catch( OutOfMemoryError oome ) { System.out.print("O"); }
        }
    }

    // so we don't lose counts, need to synchronize on some object, not primitive
    // Created "countLock" to show how this can work.
    // Comment out the synchronized and ending {, see that we lose counts.

//    public synchronized void update(int i){   // use A
    public void update(int i){                  // remove this when adding A
//      synchronized(countLock){            // or B
//      synchronized(this){             // or C
        raceCount = raceCount + 1;
        raceList.add( i );      // use Vector  
//          }           // end block for B or C
    }   // end update

    }   // end RaceThread inner class


} // end RaceCondition outter class

1
Sincronizzazione con '(questo)' non lavoro, e che non 'utilizza il thread corrente come oggetto sincronizzazione', a meno che l'oggetto corrente è di una classe che estende Thread. -1
Marchese di Lorne il
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.