Почему этот класс не является потокобезопасным?

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

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

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

Может ли кто-нибудь объяснить, почему выше класс не является потокобезопасным?

Ответ 1

Так как метод increment static, он будет синхронизироваться с объектом класса для ThreadSafeClass. Метод decrement не является статическим и будет синхронизироваться в экземпляре, используемом для его вызова. I.e., они будут синхронизироваться на разных объектах, и, таким образом, два разных потока могут одновременно выполнять методы. Поскольку операции ++ и -- не являются атомарными, класс не является потокобезопасным.

Кроме того, поскольку count является static, его изменение из decrement, которое является синхронным методом экземпляра, является небезопасным, поскольку оно может быть вызвано в разных экземплярах и одновременно изменить count таким образом.

Ответ 2

У вас есть два синхронизированных метода, но один из них является статическим, а другой - нет. При доступе к синхронизированному методу, на основе его типа (статического или нестатического), другой объект будет заблокирован. Для статического метода блокировка будет помещаться в объект класса, а для нестатического блока - блокировка будет помещена в экземпляр класса, который запускает метод. Поскольку у вас есть два разных заблокированных объекта, вы можете иметь два потока, которые одновременно изменяют один и тот же объект.

Ответ 3

Может ли кто-нибудь объяснить, почему выше класс не является потокобезопасным?

  • increment является статическим, синхронизация будет выполняться в самом классе.
  • decrement не является статическим, синхронизация будет выполняться при создании экземпляра объекта, но это не гарантирует ничего, поскольку count является статическим.

Я хотел бы добавить, что для объявления потокобезопасного счетчика я считаю, что самый простой способ - использовать AtomicInteger вместо примитивного int.

Позвольте мне перенаправить вас в java.util.concurrent.atomic package-info.

Ответ 4

  • decrement блокирует другую вещь до increment, поэтому они не мешают друг другу работать.
  • Вызов decrement в одном экземпляре блокирует другую вещь для вызова decrement в другом экземпляре, но они влияют на одно и то же.

Первое означает, что перекрывающиеся вызовы increment и decrement могут привести к отмене (правильному), приращению или декрету.

Второе означает, что два перекрывающих вызова на decrement в разных экземплярах могут приводить к двойному декременту (правильному) или одному декрету.

Ответ 5

Ответы других довольно хорошо объясняют причину. Я просто добавляю что-то, чтобы суммировать 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();

synchronized на fun1 и fun2 синхронизируется на уровне объекта экземпляра. synchronized on fun4 синхронизируется на уровне объектов класса. Это означает:

  • Когда 2 потока вызывают a1.fun1() в то же время, последний вызов будет заблокирован.
  • Когда поток 1 вызывает a1.fun1() и поток 2 вызывает a1.fun2() в то же время, последний вызов будет заблокирован.
  • Когда поток 1 вызывает a1.fun1() и поток 2 вызывает a1.fun3() в одно и то же время, без блокировки, два метода будут выполняться в одно и то же время.
  • Когда поток 1 вызывает A.fun4(), если другие потоки вызывают A.fun4() или A.fun5() в то же время, последние вызовы будут заблокированы, так как synchronized on fun4 является уровнем класса.
  • Когда поток 1 вызывает A.fun4(), поток 2 вызывает a1.fun1() в то же время, без блокировки, два метода будут выполняться в одно и то же время.

Ответ 6

Поскольку два разных метода: один - это уровень экземпляра, а другой - уровень класса, поэтому вам нужно заблокировать 2 разных объекта, чтобы сделать его ThreadSafe

Ответ 7

Как объясняется в других ответах, ваш код не защищен потоком, поскольку статический метод increment() блокирует контроль класса и нестатический метод decrement() блокирует монитор объекта.

В этом примере кода лучшее решение существует без использования ключевого слова synchronzed. Вы должны использовать AtomicInteger для обеспечения безопасности Thread.

Потоковая защита с помощью 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();
    }

}