Perché questa classe non è thread-safe?


94
class ThreadSafeClass extends Thread
{
     private static int count = 0;

     public synchronized static void increment()
     {
         count++;
     }

     public synchronized void decrement()
     {
         count--;
     }
}

Qualcuno può spiegare perché la classe sopra non è thread-safe?


6
Non so Java, ma sembra che ciascuno di questi metodi sia individualmente thread-safe, ma potresti avere un thread in ciascuno dei metodi allo stesso tempo. Forse se hai un unico metodo che accetta bool ( increment) sarebbe thread-safe. O se hai usato qualche oggetto di blocco. Come ho detto, non so nulla di Java: il mio commento deriva dalla conoscenza di C #.
Wai Ha Lee


Inoltre non conosco molto bene Java, ma per sincronizzare l'accesso a una variabile statica, synchronizeddovrebbe essere utilizzato solo nei metodi statici. Quindi, a mio parere, anche se rimuovi il incrementmetodo, non è ancora sicuro per i thread poiché due istanze (che hanno accesso sincronizzato solo tramite la stessa istanza) possono chiamare il metodo contemporaneamente.
Onur

4
È thread-safe finché non crei mai un'istanza della classe.
Benjamin Gruenbaum

1
Perché pensi che non sia thread-safe.
Raedwald

Risposte:


134

Poiché il incrementmetodo è static, si sincronizzerà sull'oggetto classe per ThreadSafeClass. Il decrementmetodo non è statico e si sincronizzerà sull'istanza utilizzata per chiamarlo. Cioè, si sincronizzeranno su oggetti diversi e quindi due thread diversi possono eseguire i metodi contemporaneamente. Poiché le operazioni ++e --non sono atomiche, la classe non è thread-safe.

Inoltre, poiché countè static, modificarlo da decrementcui è un metodo di istanza sincronizzato non è sicuro poiché può essere chiamato su istanze diverse e modificare countcontemporaneamente in questo modo.


12
Si può aggiungere, poiché countè che staticavere un metodo di istanza decrement()è sbagliato, anche se non c'era alcun static increment()metodo, poiché due thread possono invocare decrement()istanze diverse modificando lo stesso contatore contemporaneamente.
Holger

1
Questa è forse una buona ragione per preferire l'utilizzo di synchronizedblocchi in generale (anche sull'intero contenuto del metodo) invece di utilizzare il modificatore sul metodo, cioè synchronized(this) { ... }e synchronized(ThreadSafeClass.class) { ... }.
Bruno

++e --non sono atomici, nemmeno accesivolatile int . Synchronizedsi prende cura del problema di lettura / aggiornamento / scrittura con ++/ --, ma la staticparola chiave è, beh, chiave qui. Buona risposta!
Chris Cirefice

Per esempio, modificare [un campo statico] da ... un metodo di istanza sincronizzato è sbagliato : non c'è nulla di intrinsecamente sbagliato nell'accesso a una variabile statica dall'interno di un metodo di istanza, e non c'è nulla di intrinsecamente sbagliato synchronizedneanche nell'accedervi da un metodo di istanza. Non aspettarti solo "sincronizzato" sul metodo dell'istanza per fornire protezione per i dati statici. L'unico problema qui è quello che hai detto nel tuo primo paragrafo: i metodi utilizzano blocchi diversi nel tentativo di proteggere gli stessi dati, e questo ovviamente non fornisce alcuna protezione.
Solomon Slow

23

Hai due metodi sincronizzati, ma uno è statico e l'altro no. Quando si accede a un metodo sincronizzato, in base al suo tipo (statico o non statico), un oggetto diverso verrà bloccato. Per un metodo statico, verrà inserito un blocco sull'oggetto Class, mentre per il blocco non statico verrà inserito un blocco sull'istanza della classe che esegue il metodo. Poiché hai due diversi oggetti bloccati, puoi avere due thread che modificano lo stesso oggetto contemporaneamente.


14

Qualcuno può spiegare perché la classe sopra non è thread-safe?

  • increment essendo statico, la sincronizzazione verrà eseguita sulla classe stessa.
  • decrementnon essendo statico, la sincronizzazione verrà eseguita sull'istanza dell'oggetto, ma ciò non protegge nulla poiché countè statico.

Vorrei aggiungere che per dichiarare un contatore thread-safe, credo che il modo più semplice sia usare AtomicIntegerinvece di un int primitivo.

Lascia che ti reindirizzi alle informazioni sul java.util.concurrent.atomicpacchetto.


7

Le risposte degli altri sono abbastanza buone spiegate il motivo. Aggiungo solo qualcosa per riassumere synchronized:

public class A {
    public synchronized void fun1() {}

    public synchronized void fun2() {}

    public void fun3() {}

    public static synchronized void fun4() {}

    public static void fun5() {}
}

A a1 = new A();

synchronizedacceso fun1ed fun2è sincronizzato a livello di oggetto istanza. synchronizedon fun4è sincronizzato a livello di oggetto di classe. Che significa:

  1. Quando 2 thread chiamano a1.fun1()contemporaneamente, quest'ultima verrà bloccata.
  2. Quando il thread 1 chiama a1.fun1()e il thread 2 chiamano a1.fun2()contemporaneamente, quest'ultima verrà bloccata.
  3. Quando il thread 1 chiama a1.fun1()e il thread 2 chiamano a1.fun3()contemporaneamente, nessun blocco, i 2 metodi verranno eseguiti allo stesso tempo.
  4. Quando il filo 1 chiamata A.fun4(), se altri thread chiamano A.fun4()o A.fun5()allo stesso tempo, questi richiedono verranno bloccate in quanto synchronizedil fun4è livello di classe.
  5. Quando il thread 1 chiama A.fun4(), il thread 2 chiama a1.fun1()allo stesso tempo, nessun blocco, i 2 metodi verranno eseguiti allo stesso tempo.

6
  1. decrementsi sta bloccando su una cosa diversa in incrementmodo che non si impediscano a vicenda di funzionare.
  2. Chiamare decrementsu un'istanza significa bloccare una cosa diversa rispetto alla chiamata decrementsu un'altra istanza, ma stanno influenzando la stessa cosa.

Il primo significa che la sovrapposizione di chiamate a incremente decrementpotrebbe comportare un annullamento (corretto), un incremento o un decremento.

Il secondo significa che due chiamate sovrapposte a decrementsu istanze diverse potrebbero comportare un doppio decremento (corretto) o un singolo decremento.


4

Poiché due metodi diversi, uno è a livello di istanza e l'altro è a livello di classe, quindi è necessario bloccare 2 oggetti diversi per renderlo ThreadSafe


1

Come spiegato in altre risposte, il codice non è thread-safe poiché il metodo statico increment()blocca il monitoraggio della classe e il metodo non statico decrement()blocca il monitoraggio dell'oggetto.

Per questo esempio di codice, esiste una soluzione migliore senza l' synchronzedutilizzo di parole chiave. Devi usare AtomicInteger per ottenere la sicurezza dei thread.

Discussione sicura utilizzando AtomicInteger:

import java.util.concurrent.atomic.AtomicInteger;

class ThreadSafeClass extends Thread {

    private static AtomicInteger count = new AtomicInteger(0);

    public static void increment() {
        count.incrementAndGet();
    }

    public static void decrement() {
        count.decrementAndGet();
    }

    public static int value() {
        return count.get();
    }

}
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.