Domanda

In Java, il modo idiomatico di dichiarare sezioni critiche nel codice è il seguente:

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}

Quasi tutti i blocchi si sincronizzano su this , ma c'è un motivo particolare per questo? Ci sono altre possibilità? Esistono buone pratiche su quale oggetto sincronizzare? (come istanze private di Object ?)

È stato utile?

Soluzione

Innanzitutto, nota che i seguenti frammenti di codice sono identici.

public void foo() {
    synchronized (this) {
        // do something thread-safe
    }
}

e

public synchronized void foo() {
    // do something thread-safe
}

fai esattamente la stessa cosa . Nessuna preferenza per nessuno dei due ad eccezione della leggibilità e dello stile del codice.

Quando sincronizzi metodi o blocchi di codice, è importante sapere perché stai facendo una cosa del genere e quale oggetto stai bloccando esattamente, e per < forte> quale scopo .

Si noti inoltre che ci sono situazioni in cui si desidera sincronizzare sul lato client blocchi di codice in cui il monitor richiesto (ovvero l'oggetto sincronizzato) non è necessariamente questo , come in questo esempio:

Vector v = getSomeGlobalVector();
synchronized (v) {
    // some thread-safe operation on the vector
}

Ti suggerisco di acquisire maggiori conoscenze sulla programmazione concorrente, ti servirà moltissimo una volta che sai esattamente cosa sta succedendo dietro le quinte. Dovresti dare un'occhiata a Programmazione concorrente in Java , un ottimo libro sull'argomento. Se desideri un rapido approfondimento sull'argomento, consulta Java Concurrency @ Sun

Altri suggerimenti

Come hanno notato i risponditori precedenti, è consigliabile sincronizzare su un oggetto di ambito limitato (in altre parole, selezionare l'ambito più restrittivo con cui è possibile cavarsela e utilizzarlo.) In particolare, sincronizzare su questo è una cattiva idea, a meno che tu non intenda consentire agli utenti della tua classe di ottenere il blocco.

Se si sceglie di sincronizzare un java.lang.String , si presenta un caso particolarmente brutto. Le stringhe possono essere (e in pratica quasi sempre) internate. Ciò significa che ogni stringa di uguale contenuto - in ENTIRE JVM - risulta essere la stessa stringa dietro le quinte. Ciò significa che se si esegue la sincronizzazione su qualsiasi stringa, un'altra sezione di codice (completamente diversa) che si blocca anche su una stringa con lo stesso contenuto, bloccherà effettivamente anche il codice.

Una volta stavo risolvendo un deadlock in un sistema di produzione e (molto dolorosamente) ho seguito il deadlock in due pacchetti open source completamente disparati che ognuno sincronizzava su un'istanza di String i cui contenuti erano entrambi " LOCK " .

Cerco di evitare la sincronizzazione su this perché ciò consentirebbe a chiunque dall'esterno che avesse un riferimento a quell'oggetto di bloccare la mia sincronizzazione. Invece, creo un oggetto di sincronizzazione locale:

public class Foo {
    private final Object syncObject = new Object();
    …
}

Ora posso usare quell'oggetto per la sincronizzazione senza paura che qualcuno rubi & # 8221; il lucchetto.

Giusto per evidenziare che ci sono anche ReadWriteLocks disponibili in Java, trovati come java.util.concurrent.locks.ReadWriteLock.

Nella maggior parte del mio utilizzo, ho separato il blocco come 'per la lettura' e 'per gli aggiornamenti'. Se si utilizza semplicemente una parola chiave sincronizzata, tutte le letture allo stesso metodo / blocco di codice verranno "accodate". Solo un thread può accedere al blocco alla volta.

Nella maggior parte dei casi, non devi mai preoccuparti di problemi di concorrenza se stai semplicemente leggendo. È quando stai scrivendo che ti preoccupi degli aggiornamenti simultanei (con conseguente perdita di dati) o che leggi durante una scrittura (aggiornamenti parziali), di cui devi preoccuparti.

Pertanto un blocco di lettura / scrittura ha più senso per me durante la programmazione multi-thread.

Ti consigliamo di sincronizzare un oggetto che può fungere da Mutex. Se l'istanza corrente (il questo riferimento) è adatta (non un Singleton, per esempio), puoi usarla, come in Java qualsiasi oggetto può fungere da Mutex.

In altre occasioni, potresti voler condividere un Mutex tra più classi, se tutte le istanze di queste classi potrebbero aver bisogno dell'accesso alle stesse risorse.

Dipende molto dall'ambiente in cui lavori e dal tipo di sistema che stai costruendo. Nella maggior parte delle applicazioni Java EE che ho visto, in realtà non c'è davvero bisogno di sincronizzazione ...

Personalmente, penso che le risposte che insistono sul fatto che non sia mai o solo raramente corretta la sincronizzazione su questo siano sbagliate. Penso che dipenda dalla tua API. Se la tua classe è un'implementazione thread-safe e tu la documenti, allora dovresti usare this . Se la sincronizzazione non deve rendere ogni istanza della classe come un intero thread sicuro nell'invocazione dei suoi metodi pubblici, allora dovresti usare un oggetto interno privato. I componenti riutilizzabili della libreria spesso rientrano nella precedente categoria: è necessario riflettere attentamente prima di non consentire all'utente di avvolgere l'API in sincronizzazione esterna.

Nel primo caso, l'utilizzo di this consente di invocare più metodi in modo atomico. Un esempio è PrintWriter, dove potresti voler generare più righe (ad esempio una traccia dello stack sulla console / logger) e garantire che appaiano insieme - in questo caso il fatto che nasconda internamente l'oggetto di sincronizzazione è una vera seccatura. Un altro esempio è rappresentato dai wrapper di raccolta sincronizzati: è necessario eseguire la sincronizzazione sull'oggetto di raccolta stesso per iterare; poiché l'iterazione consiste in più invocazioni di metodi che non puoi proteggerla totalmente internamente.

In quest'ultimo caso, utilizzo un oggetto semplice:

private Object mutex=new Object();

Tuttavia, dopo aver visto molti dump JVM e tracce di stack che dicono che un blocco è "un'istanza di java.lang.Object ()" Devo dire che l'uso di una classe interna potrebbe spesso essere più utile, come altri hanno suggerito.

Ad ogni modo, questo vale i miei due bit.

Modifica: un'altra cosa, durante la sincronizzazione su this preferisco sincronizzare i metodi e mantenerli molto granulari. Penso che sia più chiaro e più conciso.

La sincronizzazione in Java spesso comporta operazioni di sincronizzazione sulla stessa istanza. La sincronizzazione su this è quindi molto idiomatica poiché this è un riferimento condiviso che è automaticamente disponibile tra diversi metodi di istanza (o sezioni di) in una classe.

L'uso di un altro riferimento specifico per il blocco, dichiarando e inizializzando un campo privato Object lock = new Object () , per esempio, è qualcosa che non ho mai avuto bisogno o usato. Penso che sia utile solo quando hai bisogno di sincronizzazione esterna su due o più risorse non sincronizzate all'interno di un oggetto, anche se proverei sempre a riformattare una situazione del genere in una forma più semplice.

Comunque, implicito (metodo sincronizzato) o esplicito sincronizzato (questo) viene usato molto, anche nelle librerie Java. È un buon linguaggio e, se applicabile, dovrebbe sempre essere la tua prima scelta.

Da cosa sincronizzi dipende da quali altri thread che potrebbero potenzialmente entrare in conflitto con questa chiamata al metodo possono sincronizzarsi.

Se this è un oggetto utilizzato da un solo thread e stiamo accedendo a un oggetto mutevole che è condiviso tra thread, un buon candidato è la sincronizzazione su quell'oggetto - la sincronizzazione su questo non ha senso poiché un altro thread che modifica quell'oggetto condiviso potrebbe non conoscere questo , ma conosce quell'oggetto.

D'altra parte la sincronizzazione su this ha senso se molti thread chiamano contemporaneamente i metodi di questo oggetto, ad esempio se siamo in un singleton.

Nota che un metodo sincronizzato spesso non è l'opzione migliore, poiché teniamo un lucchetto per tutto il tempo in cui il metodo viene eseguito. Se contiene un consumo di tempo ma parti thread-safe e una parte non sicura che richiede molto tempo, la sincronizzazione con il metodo è molto sbagliata.

  

Quasi tutti i blocchi si sincronizzano su questo, ma c'è un motivo particolare per questo? Ci sono altre possibilità?

Questa dichiarazione sincronizza l'intero metodo.

private synchronized void doSomething() {

Questa dichiarazione ha sincronizzato una parte del blocco di codice anziché l'intero metodo.

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}

Dalla documentazione di Oracle pagina

la sincronizzazione di questi metodi ha due effetti:

Innanzitutto, 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 completato con l'oggetto.

  

Ci sono altre possibilità? Esistono buone pratiche su quale oggetto sincronizzare? (come istanze private di Object?)

Esistono molte possibilità e alternative alla sincronizzazione. Puoi rendere sicuro il tuo thread di codice utilizzando la concorrenza di alto livello API (disponibile dalla versione JDK 1.5)

Lock objects
Executors
Concurrent collections
Atomic variables
ThreadLocalRandom

Fai riferimento alle domande SE seguenti per maggiori dettagli:

Synchronization vs Lock

Evitare la sincronizzazione (questa) in Java?

le migliori pratiche sono di creare un oggetto solo per fornire il blocco:

private final Object lock = new Object();

private void doSomething() {
  // thread-safe code
  synchronized(lock) {
    // thread-unsafe code
  }
  // thread-safe code
}

In questo modo sei sicuro, che nessun codice chiamante potrà mai bloccare il tuo metodo con una linea non intenzionale sincronizzata (yourObject) .

( Crediti a @jared e @ yuval-adam che lo hanno spiegato in maggiori dettagli sopra. )

La mia ipotesi è che la popolarità dell'uso di this nei tutorial sia venuta dai primi Sun javadoc: https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top