2009-06-11 14 views
10

Nel mio progetto java, quasi tutti i metodi non statici che ho scritto sono synchronized. Ho deciso di sistemare alcuni codici oggi rimuovendo la maggior parte delle parole chiave synchronized. Proprio lì ho creato diversi problemi di threading che ci sono voluti un po 'per risolvere, senza aumentare le prestazioni. Alla fine ho ripristinato tutto.Qualche ragione per non schiaffeggiare la parola chiave 'sincronizzata' ovunque?

Non vedo nessun altro scrivere codice con "synchronized" ovunque. Quindi, c'è qualche motivo per cui non dovrebbe avere "synchronized" ovunque?

Cosa succede se non mi interessa troppo le prestazioni (ad esempio, il metodo non viene chiamato più di una volta ogni pochi secondi)?

+13

Lo stesso motivo per cui non realizzano gli aeroplani dal materiale della scatola nera ... –

risposta

26

Naturalmente - prestazioni. I monitor hanno un costo.

La risposta non è né rimuovere né aggiungere la sincronizzazione in modo casuale. Meglio leggere qualcosa come "Java Concurrency In Practice" di Brian Goetz o "Java Threads" di Doug Lea per capire come farlo correttamente. E impara bene i nuovi pacchetti concorrenti, ovviamente.

Il multithreading è molto più della parola chiave sincronizzata.

+4

Ci sono preoccupazioni molto più grandi del solo costo della sincronizzazione. – Eddie

+11

Preoccupazioni più grandi come le persone che aggiungono la parola chiave sincronizzata ai metodi senza capire veramente perché o cosa fa. Mi preoccuperei di questo. – izb

+4

Plus: una dose eccessiva di sincronizzazione sincronizzata potrebbe causare blocchi morti – Dirk

2

Solo che quasi certamente rallenterà le cose.

Ogni oggetto sincronizzato deve eseguire un controllo di qualche tipo per assicurarsi che non sia già in uso e quindi impostare i flag quando è in uso e ripristinarli al termine.

Questo richiede tempo. Probabilmente non noterai un cambiamento funzionale, ma, se le prestazioni sono importanti, devi stare attento.

7

Il punto di prestazione è già stato indicato.

Inoltre, ricorda che tutti i thread ottengono una copia diversa dello stack. Pertanto, se un metodo utilizza solo variabili create all'interno di tale metodo e non c'è accesso al mondo esterno (ad esempio handle di file, socket, connessioni DB, ecc.), Non è possibile che si verifichino problemi di threading.

+0

Stavo per dire esattamente questo. –

36

Se si sincronizza indiscriminatamente, si rischia anche di creare un numero deadlock.

Supponiamo di avere due classi, Foo e Bar, entrambe con un metodo sincronizzato doSomething(). Supponiamo inoltre che ogni classe abbia un metodo sincronizzato che prenda un'istanza dell'altra classe come parametro.

public class Foo { 

    synchronized void doSomething() { 
     //code to doSomething 
    } 

    synchronized void doSomethingWithBar(Bar b) { 
     b.doSomething(); 
    } 

} 

public class Bar { 

    synchronized void doSomething() { 
     //code to doSomething 
    } 

    synchronized void doSomethingWithFoo(Foo f) { 
     f.doSomething(); 
    } 
} 

Si può vedere che se si dispone di un'istanza di Foo e un'istanza di Bar, sia cercando di eseguire i loro metodi doSomethingWith* a vicenda, allo stesso tempo, un deadlock può verificarsi.

di forzare la situazione di stallo, è possibile inserire un sonno in entrambi i doSomethingWith* metodi (utilizzando Foo come l'esempio):

synchronized void doSomethingWithBar(Bar b) { 
    try { 
     Thread.sleep(10000); 
    } catch (InterruptedException ie) { 
     ie.printStackTrace(); 
    } 

    b.doSomething(); 
} 

nel metodo principale, si inizia due thread per completare l'esempio:

public static void main(String[] args) { 
    final Foo f = new Foo(); 
    final Bar b = new Bar(); 
    new Thread(new Runnable() { 
     public void run() { 
      f.doSomethingWithBar(b); 
     } 
    }).start(); 

    new Thread(new Runnable() { 
     public void run() { 
      b.doSomethingWithFoo(f); 
     } 
    }).start(); 
} 
+4

Anche senza deadlock è ancora possibile ottenere grossi problemi. Sono appena stato colpito da un'operazione I/O all'interno di un blocco sincronizzato che viene chiamato migliaia di volte alla settimana e richiede circa 20 millisecondi. Nessun problema. Ma poi per qualche strana ragione un paio di volte alla settimana ci vogliono 30 minuti. Ahia! –

12

Se pensi che mettere la parola chiave "sincronizzata" ovunque sia una buona soluzione, anche ignorando le prestazioni, allora non capisci esattamente cosa sta succedendo e non dovresti usarlo. Consiglio vivamente di leggere l'argomento prima di immergermi in esso senza direzione.

Il threading è un argomento incredibilmente difficile da padroneggiare ed è davvero importante capire perché stai facendo le cose. Altrimenti ti ritroverai con un sacco di codice che funziona per caso piuttosto che per design. Questo finirà per causare dolore.

+6

o, meglio ancora, non usare thread a meno che tu non sia assolutamente, assolutamente positivo! –

6

Molto meglio che inserire synchronized ovunque è necessario ragionare attentamente su invarianti di cui le classi hanno bisogno, quindi sincronizzare quanto basta per garantire tali invarianti. Se avete più di-sincronizzazione, si creano due rischi:

  1. deadlock
  2. problemi liveness

Deadlock tutti conoscono. I problemi di Liveness non sono legati al costo della sincronizzazione, ma al fatto che se sincronizzi globalmente tutto in un'applicazione multithread, molti thread saranno bloccati in attesa di ottenere un monitor perché un altro thread sta toccando qualcosa di non correlato ma usando lo stesso tenere sotto controllo.

Se si desidera eseguire lo schiaffo di una parola chiave ovunque per motivi di sicurezza, si consiglia di utilizzare final anziché synchronized. :) Tutto ciò che è possibile fare final contribuisce a migliorare la sicurezza dei thread e un ragionamento più semplice su quali invarianti devono essere gestiti dai blocchi. Per fare un esempio, diciamo che avete questa classe banale:

public class SimpleClass { 
    private volatile int min, max; 
    private volatile Date startTime; 
    // Assume there are many other class members 

    public SimpleClass(final int min, final int max) { 
     this.min = min; 
     this.max = max; 
    } 
    public void setMin(final int min) { 
     // set min and verify that min <= max 
    } 
    public void setMax(final int max) { 
     // set max and verify that min <= max 
    } 
    public Date getStartTime() { 
     return startTime; 
    } 
} 

Con la classe di cui sopra, quando si imposta minima o massima, è necessario sincronizzare. Il codice sopra è rotto. Quale invariante ha questa classe? È necessario garantire che min <= max in qualsiasi momento, anche quando più thread chiamano setMin o setMax.

Supponendo che questo è un grande classe con molte variabili e si sincronizza tutto, poi se un thread chiama setMin e un altro thread chiama getStartTime, il secondo thread sarà inutilmente bloccato fino setMin rendimenti. Se lo fai con una classe con molti membri e supponendo che solo pochi di questi membri siano coinvolti in invarianti che devono essere difesi, la sincronizzazione di tutto causerà molti problemi di liveness di questo tipo.

1

Diciamo che avete questo esempio

 

synchronized (lock){ 
    doSomething(); 
} 
 

Se avete diversi thread bloccato non è possibile interrompere. È preferibile utilizzare Bloccare oggetti anziché "sincronizzati" perché si dispone di un controllo migliore degli interrupt che possono verificarsi. Ovviamente come tutti suggerito ci sono anche alcuni problemi di prestazioni con "sincronizzati" e se li usi eccessivamente puoi avere deadlock, livelock, ecc.

2

Non dovresti, e probabilmente hai bisogno di riconsiderare il tuo design. Dalla mia esperienza, la sincronizzazione a questo livello non è solo scalabile. Potrebbe risolvere i tuoi problemi immediati, ma non aiuterà a correggere l'integrità complessiva dell'applicazione.

Ad esempio: anche se si dispone di una raccolta "sincronizzata", un thread potrebbe voler chiamare due metodi "sincronizzati" in una volta sola, ma non si vuole che un altro thread visualizzi il risultato intermedio. Quindi qualsiasi chiamante di un'API "sincronizzata" a grana fine deve essere esso stesso a conoscenza di ciò, che degrada pesantemente l'usabilità (programmabile) di questa API.

Per iniziare, è preferibile utilizzare thread di lavoro. Isolare attività specifiche di lunga durata in modo che i thread separati prendano un blocco di input immutabile e, una volta terminato, accodano il risultato nella coda di messaggi del thread principale (che ovviamente dovrebbe essere sincronizzato). Il thread principale può eseguire il polling o aspettare esplicitamente un segnale se non ha nient'altro da fare. I dettagli specifici dipendono dai requisiti di prestazioni e dal design dell'applicazione.

A lungo termine, provare a comprendere le implementazioni del modello di transazione ACID delle implementazioni del database o della concorrenza di passaggio dei messaggi, ... ad esempio, il linguaggio di Erlang ti darà molta ispirazione. Si tratta di modelli di sincronizzazione che si sono dimostrati scalabili. Prendi il meglio di loro e prova ad applicare questi concetti all'applicazione multithread su cui lavori.

0

Se le prestazioni non sono un problema, perché non si utilizza un thread e quindi è possibile rimuovere in modo sicuro tutti "sincronizzati" Se si sincronizza tutto, si ottiene effettivamente la stessa cosa che può fare solo un thread qualsiasi cosa, ma in un modo molto più complicato e soggetto a errori.

Chiediti, perché stai utilizzando più di un thread?

Problemi correlati