Domanda

Ogni volta che viene visualizzata una domanda su SO sulla sincronizzazione Java, alcune persone sono ansiose di sottolineare che sincronizzato (questo) dovrebbe essere evitato. Invece, sostengono, è preferibile un blocco su un riferimento privato.

Alcuni dei motivi indicati sono:

Altre persone, incluso me, sostengono che sincronizzato (questo) è un linguaggio usato molto (anche nelle librerie Java), è sicuro e ben compreso. Non dovrebbe essere evitato perché hai un bug e non hai idea di cosa stia succedendo nel tuo programma multithread. In altre parole: se è applicabile, allora usalo.

Sono interessato a vedere alcuni esempi del mondo reale (niente roba da foobar) in cui è preferibile evitare un blocco su this quando sincronizzato (this) farebbe anche il lavoro .

Pertanto: dovresti sempre evitare sincronizzato (questo) e sostituirlo con un lucchetto su un riferimento privato?


Alcune ulteriori informazioni (aggiornate man mano che vengono fornite le risposte):

  • stiamo parlando della sincronizzazione dell'istanza
  • vengono considerati sia il metodo implicito ( sincronizzato ) sia la forma esplicita di sincronizzato (questo)
  • se citate Bloch o altre autorità sull'argomento, non tralasciate le parti che non vi piacciono (ad es. Java efficace, oggetto su Sicurezza thread: In genere è il blocco sull'istanza stessa, ma ci sono eccezioni.)
  • se hai bisogno di granularità nel tuo blocco diverso da sincronizzato (questo) , allora sincronizzato (questo) non è applicabile quindi non è questo il problema
È stato utile?

Soluzione

Tratterò ogni punto separatamente.

  1.   

    Alcuni codici malvagi possono rubare il tuo lucchetto (molto popolare questo, ha anche un   & Quot; accidentalmente " variante)

    Sono più preoccupato per accidentalmente . Ciò che equivale a questo è che questo uso di this fa parte dell'interfaccia esposta della tua classe e deve essere documentata. A volte è richiesta la capacità di altri codici di utilizzare il blocco. Questo è vero per cose come Collections.synchronizedMap (vedi il javadoc).

  2.   

    Tutti i metodi sincronizzati all'interno della stessa classe usano esattamente lo stesso   blocco, che riduce la velocità effettiva

    Questo è un pensiero eccessivamente semplicistico; semplicemente sbarazzarsi di sincronizzato (questo) non risolverà il problema. Una corretta sincronizzazione della velocità effettiva richiederà più attenzione.

  3.   

    Stai esponendo (inutilmente) troppe informazioni

    Questa è una variante di # 1. L'uso di sincronizzato (this) fa parte della tua interfaccia. Se non vuoi / hai bisogno di questo esposto, non farlo.

Altri suggerimenti

Bene, in primo luogo va sottolineato che:

public void blah() {
  synchronized (this) {
    // do stuff
  }
}

è semanticamente equivalente a:

public synchronized void blah() {
  // do stuff
}

che è uno dei motivi per non usare sincronizzato (questo) . Potresti sostenere che puoi fare cose intorno al blocco sincronizzato (questo) . Il solito motivo è cercare di evitare di dover fare il controllo sincronizzato, il che porta a tutti i tipi di problemi di concorrenza, in particolare il doppio problema di blocco controllato , che dimostra quanto sia difficile rendere un thread thread relativamente semplice.

Un blocco privato è un meccanismo difensivo, che non è mai una cattiva idea.

Inoltre, come hai accennato, i blocchi privati ??possono controllare la granularità. Una serie di operazioni su un oggetto potrebbe non essere completamente correlata a un'altra ma sincronizzato (questo) escluderà reciprocamente l'accesso a tutti.

sincronizzato (questo) non ti dà proprio nulla.

Mentre usi la sincronizzazione (questa) stai usando l'istanza della classe come un blocco stesso. Ciò significa che mentre il blocco viene acquisito da thread 1 , il thread 2 dovrebbe attendere.

Supponi il seguente codice:

public void method1() {
    // do something ...
    synchronized(this) {
        a ++;      
    }
    // ................
}


public void method2() {
    // do something ...
    synchronized(this) {
        b ++;      
    }
    // ................
}

Metodo 1 che modifica la variabile a e metodo 2 che modifica la variabile b , la modifica simultanea della stessa variabile con due thread dovrebbe essere evitata ed è così. MA mentre thread1 modifica a e thread2 modificando b può essere eseguito senza alcuna condizione di competizione.

Sfortunatamente, il codice sopra riportato non lo consentirà poiché stiamo usando lo stesso riferimento per un blocco; Ciò significa che i thread, anche se non sono in condizioni di gara, dovrebbero attendere e ovviamente il codice sacrifica la concorrenza del programma.

La soluzione è utilizzare 2 blocchi diversi per due variabili diverse:

public class Test {

    private Object lockA = new Object();
    private Object lockB = new Object();

    public void method1() {
        // do something ...
        synchronized(lockA) {
            a ++;      
        }
        // ................
    }


    public void method2() {
        // do something ...
        synchronized(lockB) {
            b ++;      
        }
        // ................
    }

}

L'esempio precedente utilizza blocchi più dettagliati (2 blocchi invece di uno ( blocco A e blocco B per le variabili a e b rispettivamente) e di conseguenza consente una migliore concorrenza, d'altro canto è diventato più complesso del primo esempio ...

Mentre sono d'accordo di non aderire ciecamente alle regole dogmatiche, il "blocco ruba" lo scenario ti sembra così eccentrico? Un thread potrebbe effettivamente acquisire il blocco sul tuo oggetto "esternamente" ( sincronizzato (theObject) {...} ), bloccando altri thread in attesa di metodi di istanza sincronizzati.

Se non credi nel codice dannoso, considera che questo codice potrebbe provenire da terze parti (ad esempio se sviluppi una sorta di server delle applicazioni).

Il " accidentale " la versione sembra meno probabile, ma come si suol dire, "fare qualcosa a prova di idiota e qualcuno inventerà un migliore idiota".

Quindi sono d'accordo con la scuola di pensiero che dipende dalla classe.


Modifica i seguenti primi 3 commenti di eljenso:

Non ho mai riscontrato il problema del furto di lucchetto, ma ecco uno scenario immaginario:

Supponiamo che il tuo sistema sia un contenitore servlet e l'oggetto che stiamo prendendo in considerazione è l'implementazione ServletContext . Il metodo getAttribute deve essere thread-safe, poiché gli attributi di contesto sono dati condivisi; quindi lo dichiari come sincronizzato . Immaginiamo anche di fornire un servizio di hosting pubblico basato sull'implementazione del container.

Sono tuo cliente e distribuisco il mio " buono " servlet sul tuo sito. Succede che il mio codice contenga una chiamata a getAttribute .

Un hacker, travestito da un altro cliente, distribuisce il suo servlet dannoso sul tuo sito. Contiene il seguente codice nel metodo init :

synchronized (this.getServletConfig().getServletContext()) {
   while (true) {}
}

Supponendo che condividiamo lo stesso contesto servlet (consentito dalle specifiche fintanto che i due servlet sono sullo stesso host virtuale), la mia chiamata su getAttribute è bloccata per sempre. L'hacker ha realizzato un DoS sul mio servlet.

Questo attacco non è possibile se getAttribute è sincronizzato su un blocco privato, poiché il codice di terze parti non può acquisire questo blocco.

Ammetto che l'esempio è inventato e una visione troppo semplicistica di come funziona un contenitore servlet, ma IMHO ne dimostra il punto.

Quindi farei la mia scelta progettuale in base alla considerazione della sicurezza: avrò il controllo completo sul codice che ha accesso alle istanze? Quale sarebbe la conseguenza di un thread che tiene un lucchetto su un'istanza indefinitamente?

Sembra esserci un diverso consenso nei campi C # e Java su questo. La maggior parte del codice Java che ho visto usa:

// apply mutex to this instance
synchronized(this) {
    // do work here
}

considerando che la maggior parte del codice C # opta probabilmente per il più sicuro:

// instance level lock object
private readonly object _syncObj = new object();

...

// apply mutex to private instance level field (a System.Object usually)
lock(_syncObj)
{
    // do work here
}

Il linguaggio C # è sicuramente più sicuro. Come accennato in precedenza, nessun accesso dannoso / accidentale al blocco può essere effettuato dall'esterno dell'istanza. Anche il codice Java ha questo rischio, ma sembra che la comunità Java abbia gravitato nel tempo verso la versione leggermente meno sicura, ma leggermente più concisa.

Questo non è inteso come uno scavo contro Java, ma solo un riflesso della mia esperienza di lavoro in entrambe le lingue.

Dipende dalla situazione.
Se esiste solo un'entità di condivisione o più di una.

Guarda l'esempio di lavoro completo qui

Una piccola introduzione.

Discussioni ed entità condivisibili
È possibile che più thread accedano alla stessa entità, ad es. Connessioni multipleTread che condividono un singolo messaggioQueue. Poiché i thread vengono eseguiti contemporaneamente, potrebbe esserci la possibilità di ignorare i propri dati da un altro, il che potrebbe essere una situazione incasinata. Quindi abbiamo bisogno di un modo per garantire che l'entità condivisibile sia accessibile solo da un thread alla volta. (CONCORRENZA).

Blocco sincronizzato
blocco sincronizzato () è un modo per garantire l'accesso simultaneo all'entità condivisibile.
Innanzitutto, una piccola analogia
Supponiamo che ci siano due persone P1, P2 (discussioni) un lavabo (entità condivisibile) all'interno di un bagno e che sia presente una porta (serratura).
Ora vogliamo che una persona usi il lavandino alla volta.
Un approccio è quello di bloccare la porta con P1 quando la porta è bloccata P2 attende fino a quando p1 completa il suo lavoro
P1 sblocca la porta
quindi solo p1 può usare il lavabo.

sintassi.

synchronized(this)
{
  SHARED_ENTITY.....
}

" questo " fornito il blocco intrinseco associato alla classe (la classe Object progettata dallo sviluppatore Java in modo tale che ciascun oggetto possa funzionare come monitor). L'approccio sopra funziona bene quando ci sono solo un'entità condivisa e più thread (1: N).
   inserisci qui la descrizione dell'immagine N entità condivisibili-thread M
Ora pensa a una situazione in cui ci sono due lavandini all'interno di un bagno e una sola porta. Se stiamo usando l'approccio precedente, solo p1 può usare un lavandino alla volta mentre p2 attenderà all'esterno. È uno spreco di risorse poiché nessuno usa B2 (lavabo).
Un approccio più saggio sarebbe quello di creare una stanza più piccola all'interno del bagno e fornire loro una porta per lavandino. In questo modo, P1 può accedere a B1 e P2 può accedere a B2 e viceversa.

washbasin1;  
washbasin2;

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

  synchronized(lock1)
  {
    washbasin1;
  }

  synchronized(lock2)
  {
    washbasin2;
  }

 inserisci qui la descrizione dell'immagine
inserisci qui la descrizione dell'immagine

Vedi di più sui thread ---- > qui

Il pacchetto java.util.concurrent ha notevolmente ridotto la complessità del mio codice thread sicuro. Ho solo prove aneddotiche da continuare, ma la maggior parte del lavoro che ho visto con sincronizzato (x) sembra reimplementare un Lock, Semaphore o Latch, ma usando i monitor di livello inferiore.

Con questo in mente, la sincronizzazione usando uno di questi meccanismi è analoga alla sincronizzazione su un oggetto interno, piuttosto che perdere un lucchetto. Questo è utile in quanto hai la certezza assoluta di controllare l'ingresso nel monitor da due o più thread.

  1. Rendi immutabili i tuoi dati se possibile ( final variabili)
  2. Se non puoi evitare la mutazione dei dati condivisi su più thread, utilizza costrutti di programmazione di alto livello [ad es. API granulare Lock ]
  

Un blocco fornisce l'accesso esclusivo a una risorsa condivisa: solo un thread alla volta può acquisire il blocco e tutto l'accesso alla risorsa condivisa richiede che il blocco sia acquisito per primo.

Codice di esempio per utilizzare ReentrantLock che implementa l'interfaccia Lock

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

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

Vantaggi di Lock over Synchronized (questo)

  1. L'uso di metodi o istruzioni sincronizzati impone che tutti i blocchi di acquisizione e rilascio avvengano in modo strutturato a blocchi.

  2. Le implementazioni di blocco forniscono funzionalità aggiuntive sull'uso di metodi e istruzioni sincronizzati fornendo

    1. Un tentativo non bloccante di acquisire un blocco ( tryLock () )
    2. Un tentativo di acquisire il blocco che può essere interrotto ( lockInterruptibly () )
    3. Un tentativo di acquisire il blocco che può scadere ( tryLock (long, TimeUnit) ).
  3. Una classe Lock può anche fornire un comportamento e una semantica molto diversi da quelli del blocco implicito del monitor, come

    1. ordini garantiti
    2. utilizzo non concorrente
    3. Rilevamento deadlock

Dai un'occhiata a questa domanda SE riguardante vari tipi di Locks :

Synchronization vs Lock

Puoi ottenere la sicurezza dei thread utilizzando l'API di concorrenza avanzata anziché i blocchi sincronizzati. Questa documentazione page fornisce buona programmazione costruisce per ottenere la sicurezza del thread.

Blocca oggetti supporta espressioni di blocco che semplificano molte applicazioni simultanee.

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

Collezioni simultanee semplificare la gestione di grandi raccolte di dati e ridurre notevolmente la necessità di sincronizzazione.

Variabili atomiche dispongono di 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.

Consulta java.util .concurrent e Pacchetti java.util.concurrent.atomic anche per altri costrutti di programmazione.

Se hai deciso che:

  • la cosa che devi fare è bloccare l'oggetto corrente; e
  • vuoi farlo bloccarlo con granularità inferiore a un intero metodo;

allora non vedo il tabù su synchronizezd (questo).

Alcune persone usano deliberatamente sincronizzato (questo) (invece di contrassegnare il metodo sincronizzato) all'interno dell'intero contenuto di un metodo perché pensano che sia "più chiaro per il lettore". su quale oggetto viene effettivamente sincronizzato. Finché le persone stanno facendo una scelta informata (ad esempio, capendo che facendo ciò, in realtà stanno inserendo bytecode extra nel metodo e questo potrebbe avere un effetto a catena su potenziali ottimizzazioni), non vedo particolarmente un problema con questo . Dovresti sempre documentare il comportamento concorrente del tuo programma, quindi non vedo il messaggio "sincronizzato" che pubblica il comportamento " argomento così convincente.

Per quanto riguarda la domanda su quale blocco dell'oggetto dovresti usare, penso che non ci sia nulla di sbagliato nel sincronizzare l'oggetto corrente se ciò fosse previsto dalla logica di ciò che stai facendo e di come sarebbe normalmente la tua classe utilizzato . Ad esempio, con una raccolta, l'oggetto che ti aspetteresti logicamente di bloccare è generalmente la raccolta stessa.

Penso che ci sia una buona spiegazione sul perché ognuna di queste sono tecniche vitali sotto il tuo controllo in un libro intitolato Java Concurrency In Practice di Brian Goetz. Rende molto chiaro un punto: devi usare lo stesso lucchetto "OVUNQUE" " per proteggere lo stato del tuo oggetto. Il metodo sincronizzato e la sincronizzazione su un oggetto spesso vanno di pari passo. Per esempio. Vector sincronizza tutti i suoi metodi. Se hai un handle per un oggetto vettoriale e hai intenzione di fare "put if absent" quindi semplicemente Vector sincronizzare i propri metodi individuali non ti proteggerà dalla corruzione dello stato. Devi sincronizzare usando sincronizzato (vectorHandle). Ciò comporterà l'acquisizione del SAME lock da parte di ogni thread che ha un handle per il vettore e proteggerà lo stato generale del vettore. Questo si chiama blocco lato client. Sappiamo infatti che il vettore sincronizza (questo) / sincronizza tutti i suoi metodi e quindi la sincronizzazione sull'oggetto vectorHandle comporterà una corretta sincronizzazione dello stato degli oggetti vettoriali. È sciocco credere di essere thread-safe solo perché si utilizza una raccolta thread-safe. Questo è precisamente il motivo per cui ConcurrentHashMap ha introdotto esplicitamente il metodo putIfAbsent - per rendere tali operazioni atomiche.

In sintesi

  1. La sincronizzazione a livello di metodo consente il blocco lato client.
  2. Se si dispone di un oggetto di blocco privato, ciò rende impossibile il blocco lato client. Questo va bene se sai che la tua classe non ha " put se assente " tipo di funzionalità.
  3. Se stai progettando una libreria - allora la sincronizzazione su questo o la sincronizzazione del metodo è spesso più saggia. Perché raramente sei in grado di decidere come verrà utilizzata la tua classe.
  4. Se Vector avesse usato un oggetto di blocco privato - sarebbe stato impossibile ottenere "put se assente" destra. Il codice client non otterrà mai un handle per il blocco privato, infrangendo così la regola fondamentale dell'utilizzo di EXACT SAME LOCK per proteggere il suo stato.
  5. La sincronizzazione su questo o sui metodi sincronizzati ha un problema, come altri hanno sottolineato: qualcuno potrebbe ottenere un blocco e non rilasciarlo mai. Tutti gli altri thread continuerebbero ad attendere il rilascio del blocco.
  6. Quindi sappi cosa stai facendo e adotta quello che è corretto.
  7. Qualcuno ha sostenuto che avere un oggetto di blocco privato offre una granularità migliore, ad es. se due operazioni non sono correlate, potrebbero essere sorvegliate da blocchi diversi con conseguente migliore produttività. Ma penso che questo sia odore di design e non odore di codice - se due operazioni sono completamente estranee perché fanno parte della classe SAME? Perché un club di classe non ha affatto funzionalità indipendenti? Può essere una classe di utilità? Hmmmm - alcuni util che forniscono la manipolazione di stringhe e la formattazione della data del calendario attraverso la stessa istanza ?? ... non ha alcun senso per me almeno !!

No, non dovresti sempre . Tuttavia, tendo ad evitarlo quando ci sono più preoccupazioni su un oggetto particolare che devono essere solo sicure rispetto a se stesse. Ad esempio, potresti avere un oggetto dati mutabile con " label " e "parent" campi; questi devono essere sicuri per i thread, ma cambiarne uno non è necessario bloccare l'altro da scrivere / leggere. (In pratica lo eviterei dichiarando i campi volatili e / o usando i wrapper AtomicFoo di java.util.concurrent).

La sincronizzazione in generale è un po 'goffa, dato che schiaccia un grosso blocco piuttosto che pensare esattamente come si potrebbe permettere ai thread di aggirarsi. L'uso di sincronizzato (this) è ancora più maldestro e antisociale, come sta dicendo " nessuno può cambiare nulla su questa classe mentre tengo il blocco " ;. Con quale frequenza devi effettivamente farlo?

Preferirei di gran lunga avere blocchi più granulari; anche se vuoi impedire che tutto cambi (forse stai serializzando l'oggetto), puoi semplicemente acquisire tutti i blocchi per ottenere la stessa cosa, inoltre è più esplicito in quel modo. Quando usi sincronizzato (questo) , non è chiaro esattamente perché stai sincronizzando o quali potrebbero essere gli effetti collaterali. Se usi sincronizzato (labelMonitor) , o ancora meglio labelLock.getWriteLock (). Lock () , è chiaro cosa stai facendo e quali sono gli effetti della tua sezione critica limitato a.

Risposta breve : devi capire la differenza e scegliere a seconda del codice.

Risposta lunga : in generale, preferirei evitare di sincronizzare (questo) per ridurre la contesa, ma i blocchi privati ??aggiungono complessità di cui devi essere consapevole. Quindi usa la sincronizzazione corretta per il lavoro giusto. Se non hai esperienza con la programmazione multi-thread, preferirei attenermi al blocco dell'istanza e leggere su questo argomento. (Detto questo: usare semplicemente sincronizza (questo) non rende automaticamente la tua classe completamente thread-safe.) Questo non è un argomento facile ma una volta che ti ci abitui, la risposta se usare < em> sincronizzazione (questo) o non viene naturalmente.

Un lucchetto viene utilizzato per visibilità o per proteggere alcuni dati da modifiche simultanee che possono portare alla competizione.

Quando devi solo eseguire operazioni di tipo primitivo per essere atomiche, ci sono opzioni disponibili come AtomicInteger e simili.

Ma supponiamo di avere due numeri interi collegati tra loro come coordinate x e y , che sono correlati tra loro e che dovrebbero essere cambiati in un atomico maniera. Quindi li proteggeresti usando lo stesso lucchetto.

Un blocco dovrebbe proteggere solo lo stato correlato. Niente di meno e niente di più. Se si utilizza sincronizzato (this) in ciascun metodo, anche se lo stato della classe non è correlato, tutti i thread dovranno affrontare la contesa anche se si aggiorna uno stato non correlato.

class Point{
   private int x;
   private int y;

   public Point(int x, int y){
       this.x = x;
       this.y = y;
   }

   //mutating methods should be guarded by same lock
   public synchronized void changeCoordinates(int x, int y){
       this.x = x;
       this.y = y;
   }
}

Nell'esempio sopra ho un solo metodo che muta sia x che y e non due metodi diversi come x e y sono correlati e se avessi dato due diversi metodi per la mutazione x e y separatamente, non sarebbe stato sicuro per i thread.

Questo esempio è solo per dimostrare e non necessariamente il modo in cui dovrebbe essere implementato. Il modo migliore per farlo sarebbe renderlo IMMMUTABILE .

Ora in contrapposizione all'esempio Point , c'è un esempio di TwoCounters già fornito da @Andreas in cui lo stato che è protetto da due diversi blocchi in quanto lo stato è non collegati tra loro.

Il processo di utilizzo di diversi blocchi per proteggere gli stati non correlati è chiamato Blocca striping o Blocca divisione

Il motivo per non sincronizzarsi su this è che a volte è necessario più di un blocco (il secondo blocco viene spesso rimosso dopo qualche riflessione aggiuntiva, ma è ancora necessario nello stato intermedio). Se blocchi questo , devi sempre ricordare quale dei due blocchi è questo ; se blocchi un oggetto privato, il nome della variabile ti dice questo.

Dal punto di vista del lettore, se vedi il blocco su questo , devi sempre rispondere alle due domande:

  1. che tipo di accesso è protetto da questo ?
  2. un blocco è davvero abbastanza, qualcuno non ha introdotto un bug?

Un esempio:

class BadObject {
    private Something mStuff;
    synchronized setStuff(Something stuff) {
        mStuff = stuff;
    }
    synchronized getStuff(Something stuff) {
        return mStuff;
    }
    private MyListener myListener = new MyListener() {
        public void onMyEvent(...) {
            setStuff(...);
        }
    }
    synchronized void longOperation(MyListener l) {
        ...
        l.onMyEvent(...);
        ...
    }
}

Se due thread iniziano longOperation () su due diverse istanze di BadObject , acquisiscono le loro serrature; quando è il momento di invocare l.onMyEvent (...) , abbiamo un deadlock perché nessuno dei thread può acquisire il blocco dell'altro oggetto.

In questo esempio possiamo eliminare il deadlock usando due blocchi, uno per operazioni brevi e uno per operazioni lunghe.

Come già detto qui il blocco sincronizzato può usare una 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 e così via.

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 "questo". E di conseguenza avremo dimensioni della funzione un po 'più grandi (non rilevanti se hai solo un numero limitato di funzioni).

Spiegazione più dettagliata della differenza che puoi trovare qui: http://www.artima.com/insidejvm/ed2/threadsynchP.html

Anche l'uso del blocco sincronizzato non è buono a causa del seguente punto di vista:

  

La parola chiave sincronizzata è molto limitata in un'area: quando si esce da un blocco sincronizzato, tutti i thread in attesa di quel blocco devono essere sbloccati, ma solo uno di quei thread riesce a prendere il blocco; tutti gli altri vedono che il blocco è stato preso e tornano allo stato bloccato. Non sono solo molti cicli di elaborazione sprecati: spesso il cambio di contesto per sbloccare un thread comporta anche il paging della memoria dal disco ed è molto, molto, costoso.

Per maggiori dettagli in quest'area ti consiglio di leggere questo articolo: http://java.dzone.com/articles/synchronized-considered

Questo è davvero solo supplementare alle altre risposte, ma se la tua principale obiezione all'utilizzo di oggetti privati ??per il blocco è che ingombra la tua classe con campi che non sono correlati alla logica aziendale, Project Lombok ha @Synchronized per generare la caldaia in fase di compilazione:

@Synchronized
public int foo() {
    return 0;
}

compila in

private final Object $lock = new Object[0];

public int foo() {
    synchronized($lock) {
        return 0;
    }
}

Un buon esempio per l'uso sincronizzato (questo).

// add listener
public final synchronized void addListener(IListener l) {listeners.add(l);}
// remove listener
public final synchronized void removeListener(IListener l) {listeners.remove(l);}
// routine that raise events
public void run() {
   // some code here...
   Set ls;
   synchronized(this) {
      ls = listeners.clone();
   }
   for (IListener l : ls) { l.processEvent(event); }
   // some code here...
}

Come puoi vedere qui, usiamo la sincronizzazione su questo per cooperare facilmente lungo (possibilmente infinito ciclo di metodo di esecuzione) con alcuni metodi sincronizzati lì.

Naturalmente può essere facilmente riscritto usando sincronizzato su campo privato. Ma a volte, quando abbiamo già qualche progetto con metodi sincronizzati (cioè classe legacy, deriviamo, sincronizzato (questa) può essere l'unica soluzione).

Dipende dal compito che vuoi fare, ma non lo userei. Inoltre, controlla se il salvataggio del thread che vuoi accompagnare non può essere fatto sincronizzando (questo) in primo luogo? Ci sono anche dei bei nell'API che potrebbe aiutarti :)

Voglio solo menzionare una possibile soluzione per riferimenti privati ??univoci in parti atomiche di codice senza dipendenze. È possibile utilizzare una hashmap statica con blocchi e un semplice metodo statico denominato atomic () che crea automaticamente i riferimenti richiesti utilizzando le informazioni sullo stack (nome completo della classe e numero di riga). Quindi è possibile utilizzare questo metodo per sincronizzare le istruzioni senza scrivere un nuovo oggetto lock.

// Synchronization objects (locks)
private static HashMap<String, Object> locks = new HashMap<String, Object>();
// Simple method
private static Object atomic() {
    StackTraceElement [] stack = Thread.currentThread().getStackTrace(); // get execution point 
    StackTraceElement exepoint = stack[2];
    // creates unique key from class name and line number using execution point
    String key = String.format("%s#%d", exepoint.getClassName(), exepoint.getLineNumber()); 
    Object lock = locks.get(key); // use old or create new lock
    if (lock == null) {
        lock = new Object();
        locks.put(key, lock);
    }
    return lock; // return reference to lock
}
// Synchronized code
void dosomething1() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 1
        ...
    }
    // other command
}
// Synchronized code
void dosomething2() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 2
        ...
    }
    // other command
}

Evita di usare sincronizzato (questo) come meccanismo di blocco: questo blocca l'intera istanza della classe e può causare deadlock. In questi casi, refactoring il codice per bloccare solo un metodo o una variabile specifici, in questo modo l'intera classe non viene bloccata. Sincronizzato può essere utilizzato a livello di metodo.
Invece di usare sincronizzato (questo) , sotto il codice mostra come potresti semplicemente bloccare un metodo.

   public void foo() {
if(operation = null) {
    synchronized(foo) { 
if (operation == null) {
 // enter your code that this method has to handle...
          }
        }
      }
    }

I miei due centesimi nel 2019 anche se questa domanda avrebbe potuto essere risolta già.

Il blocco di 'this' non è male se sai cosa stai facendo ma dietro la scena il blocco di 'this' è (cosa che sfortunatamente ciò che consente la parola chiave sincronizzata nella definizione del metodo).

Se vuoi davvero che gli utenti della tua classe siano in grado di 'rubare' il tuo blocco (cioè impedire ad altri thread di gestirlo), vuoi davvero che tutti i metodi sincronizzati attendano mentre è in esecuzione un altro metodo di sincronizzazione e così via. Dovrebbe essere intenzionale e ben ponderato (e quindi documentato per aiutare i tuoi utenti a capirlo).

Per elaborare ulteriormente, al contrario devi sapere cosa stai "guadagnando" (o "perdere") se blocchi un blocco non accessibile (nessuno può "rubare" il tuo blocco, hai il controllo totale e presto...).

Il problema per me è che la parola chiave sincronizzata nella firma della definizione del metodo rende troppo facile per i programmatori non pensare a cosa bloccare a che cosa è una cosa molto importante a cui pensare se non lo fai non voglio incontrare problemi in un programma multi-thread.

Non si può sostenere che "tipicamente" non si desidera che gli utenti della propria classe siano in grado di fare queste cose o che "tipicamente" si desideri ... Dipende da quale funzionalità si sta codificando. Non è possibile stabilire una regola del pollice poiché non è possibile prevedere tutti i casi d'uso.

Prendi in considerazione ad es. il printwriter che usa un lock interno ma poi le persone fanno fatica a usarlo da più thread se non vogliono che il loro output si interfogli.

Se il tuo lucchetto è accessibile al di fuori della classe o no, la tua decisione di programmatore dipende dalla funzionalità della classe. Fa parte dell'API. Ad esempio, non puoi passare da sincronizzato (questo) a sincronizzato (provateObjet) senza rischiare di interrompere le modifiche al codice che lo utilizza.

Nota 1: So che puoi ottenere tutto ciò che (sincronizzato) "realizza" usando un oggetto di blocco esplicito ed esponendolo, ma penso che non sia necessario se il tuo comportamento è ben documentato e in realtà sai cosa bloccare su "questo" significa.

Nota 2: non concordo con l'argomento secondo cui se un codice ruba accidentalmente il tuo lucchetto è un bug e devi risolverlo. Questo in un certo senso è lo stesso argomento del dire che posso rendere pubblici tutti i miei metodi anche se non sono fatti per essere pubblici. Se qualcuno sta 'accidentalmente' chiamando il mio inteso come metodo privato è un bug. Perché abilitare questo incidente in primo luogo !!! Se la capacità di rubare il lucchetto è un problema per la tua classe, non permetterlo. Semplice come quello.

Penso che i punti uno (qualcun altro usa il tuo lucchetto) e due (tutti i metodi che usano lo stesso lucchetto inutilmente) possono accadere in qualsiasi applicazione abbastanza grande. Soprattutto quando non c'è una buona comunicazione tra gli sviluppatori.

Non è una pietra miliare, è principalmente un problema di buone pratiche e di prevenzione degli errori.

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