Dlaczego ta klasa nie jest bezpieczna wątkowo?

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

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

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

Czy ktoś może wyjaśnić, dlaczego powyższa klasa nie jest bezpieczna wątkowo?

das kinder
źródło
6
Nie wiem o Javie, ale wygląda na to, że każda z tych metod jest indywidualnie bezpieczna dla wątków, ale możesz mieć wątek w każdej z nich jednocześnie. Może jeśli masz jedną metodę, która przyjmuje bool ( increment), będzie to bezpieczne wątkowo. Lub jeśli użyłeś jakiegoś obiektu blokującego. Jak powiedziałem, nie znam Javy - mój komentarz wynika ze znajomości języka C #.
Wai Ha Lee
Nie znam też dobrze Javavery, ale aby zsynchronizować dostęp do zmiennej statycznej, synchronizednależy jej używać tylko w metodach statycznych. Więc moim zdaniem, nawet jeśli usuniesz incrementmetodę, nadal nie jest ona bezpieczna wątkowo, ponieważ dwie instancje (które mają tylko zsynchronizowany dostęp przez tę samą instancję) mogą wywoływać metodę jednocześnie.
Onur
4
Jest bezpieczny wątkowo, o ile nigdy nie utworzysz instancji tej klasy.
Benjamin Gruenbaum
1
Jak myślisz, dlaczego nie jest bezpieczny dla wątków.
Raedwald

Odpowiedzi:

134

Ponieważ incrementmetoda jest taka static, będzie synchronizować obiekt klasy dla ThreadSafeClass. decrementMetoda nie jest statyczny i zsynchronizuje na wystąpienie używane to nazwać. Oznacza to, że będą synchronizować się na różnych obiektach, a zatem dwa różne wątki mogą wykonywać te metody w tym samym czasie. Ponieważ operacje ++i --nie są atomowe, klasa nie jest bezpieczna wątkowo.

Ponadto, ponieważ countjest static, modyfikowanie jej, z decrementktórej jest to metoda zsynchronizowanej instancji , jest niebezpieczne, ponieważ można ją wywołać w różnych instancjach i countjednocześnie modyfikować w ten sposób.

K Erlandsson
źródło
12
Możesz dodać, ponieważ countjest static, posiadanie metody wystąpienia decrement()jest niewłaściwe, nawet jeśli nie było static increment()metody, ponieważ dwa wątki mogą wywoływać decrement()w różnych instancjach, modyfikując ten sam licznik jednocześnie.
Holger
1
To być może dobry powód, aby wolą używać synchronizedbloków w ogóle (nawet na całej zawartości metoda) zamiast przy użyciu modyfikatora na metodzie, czyli synchronized(this) { ... }a synchronized(ThreadSafeClass.class) { ... }.
Bruno
++i --nie są atomowe, nawet włączonevolatile int . Synchronizedrozwiązuje problem odczytu / aktualizacji / zapisu z ++/ --, ale staticsłowem kluczowym jest tutaj klucz . Dobra odpowiedź!
Chris Cirefice
Re, modyfikacja [pola statycznego] z ... metody zsynchronizowanej instancji jest nieprawidłowa : nie ma nic z natury złego w dostępie do zmiennej statycznej z poziomu metody instancji, a także nie ma nic złego w dostępie do niej z synchronizedmetody instancji. Po prostu nie oczekuj, że metoda „zsynchronizowana” w metodzie instancji zapewni ochronę danych statycznych. Jedynym problemem jest to, co powiedziałeś w pierwszym akapicie: metody używają różnych blokad w celu ochrony tych samych danych, a to oczywiście nie zapewnia żadnej ochrony.
Solomon Slow
23

Masz dwie zsynchronizowane metody, ale jedna z nich jest statyczna, a druga nie. Podczas uzyskiwania dostępu do metody zsynchronizowanej, na podstawie jej typu (statyczna lub niestatyczna), inny obiekt zostanie zablokowany. W przypadku metody statycznej blokada zostanie nałożona na obiekt Class, natomiast w przypadku bloku niestatycznego blokada zostanie nałożona na wystąpienie klasy, która uruchamia metodę. Ponieważ masz dwa różne zablokowane obiekty, możesz mieć dwa wątki, które jednocześnie modyfikują ten sam obiekt.

Slimu
źródło
14

Czy ktoś może wyjaśnić, dlaczego powyższa klasa nie jest bezpieczna wątkowo?

  • increment ponieważ jest statyczny, synchronizacja zostanie wykonana na samej klasie.
  • decrementponieważ nie jest statyczny, synchronizacja zostanie wykonana na instancji obiektu, ale to nie zapewnia niczego, ponieważ countjest statyczne.

Chciałbym dodać, że aby zadeklarować licznik bezpieczny dla wątków, moim zdaniem najprostszym sposobem jest użycie AtomicIntegerzamiast pierwotnego int.

Przekieruję cię do informacji o java.util.concurrent.atomicpaczce.

Jean-François Savard
źródło
7

Odpowiedzi innych są całkiem dobre, wyjaśniają powód. Dodam tylko coś do podsumowania 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();

synchronizedwłączony fun1i fun2jest synchronizowany na poziomie obiektu instancji. synchronizedon fun4jest synchronizowany na poziomie obiektu klasy. Co znaczy:

  1. Gdy 2 wątki dzwonią a1.fun1()w tym samym czasie, drugie połączenie zostanie zablokowane.
  2. Gdy wywołanie wątku 1 a1.fun1()i wywołanie wątku 2 a1.fun2()w tym samym czasie, drugie wywołanie zostanie zablokowane.
  3. Gdy wywołanie wątku 1 a1.fun1()i wywołanie wątku 2 a1.fun3()w tym samym czasie, bez blokowania, dwie metody zostaną wykonane w tym samym czasie.
  4. Przy wywołaniu wątku 1 A.fun4(), w przypadku wywołania innych wątków A.fun4()lub A.fun5()w tym samym czasie, ostatnie wywołania będą blokowane, ponieważ synchronizedon fun4jest na poziomie klasy.
  5. Gdy wywołanie wątku 1, wywołanie A.fun4()wątku 2 a1.fun1()w tym samym czasie, bez blokowania, 2 metody zostaną wykonane w tym samym czasie.
coderz
źródło
6
  1. decrementblokuje się na innej rzeczy, aby incrementnie przeszkadzać sobie nawzajem.
  2. Wywołanie decrementjednej instancji blokuje inną rzecz niż wywołanie decrementinnej instancji, ale wpływa to na to samo.

Pierwsza oznacza, że ​​nakładające się wywołania incrementi decrementmogą skutkować anulowaniem (poprawnym), zwiększeniem lub zmniejszeniem.

Drugi oznacza, że ​​dwa nakładające się wywołania decrementw różnych instancjach mogą spowodować podwójne zmniejszenie (poprawne) lub pojedyncze zmniejszenie.

Jon Hanna
źródło
4

Ponieważ dwie różne metody, jedna to poziom instancji, a druga to poziom klasy, musisz zablokować 2 różne obiekty, aby było to ThreadSafe

jaleel_quest
źródło
1

Jak wyjaśniono w innych odpowiedziach, Twój kod nie jest bezpieczny dla wątków, ponieważ metoda statyczna increment()blokuje Monitor klas i metoda niestatyczna decrement()blokuje Monitor obiektów.

W przypadku tego przykładu kodu istnieje lepsze rozwiązanie bez synchronzedużycia słów kluczowych. Aby zapewnić bezpieczeństwo wątków, musisz użyć AtomicInteger .

Bezpieczny wątek przy użyciu 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();
    }

}
Ravindra babu
źródło