Чому цей клас не є безпечним для потоків?


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

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

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

Хто-небудь може пояснити, чому вищезгаданий клас не є безпечним для потоків?


6
Я не знаю про Java, але схоже, кожен із цих методів є індивідуально безпечним для потоку, але ви можете мати потік у кожному з методів одночасно. Можливо, якщо у вас є один метод, який приймає bool ( increment), це буде безпечно для потоку. Або якщо ви використовували якийсь фіксуючий об’єкт. Як я вже сказав, я не знаю про Java - мій коментар походить від знань на C #.
Вай Ха Лі


Я також погано знаю Javavery, але для синхронізації доступу до статичної змінної synchronizedслід використовувати лише статичні методи. Отже, на мою думку, навіть якщо ви видалите incrementметод, він все одно не є безпечним для потоків, оскільки два екземпляри (які мають лише синхронізований доступ через один і той же екземпляр) можуть одночасно викликати метод.
Онур

4
Це безпечно для потоку, якщо ви ніколи не створюєте екземпляр класу.
Бенджамін Груенбаум

1
Чому, на вашу думку, це не безпечно для ниток.
Редвальд,

Відповіді:


134

Оскільки incrementметод є, staticвін буде синхронізуватися з об'єктом класу для ThreadSafeClass. decrementМетод не є статичним і синхронізувати екземпляр використовується для його виклику. Тобто, вони будуть синхронізуватися на різних об’єктах, і, отже, два різні потоки можуть виконувати методи одночасно. Оскільки операції ++and --не є атомними, клас не є безпечним для потоку.

Крім того, оскільки countє static, модифікувати його, з decrementякого є синхронізованим методом екземпляра , небезпечно, оскільки його можна викликати в різних екземплярах і countодночасно змінювати таким чином.


12
Ви можете додати, оскільки countє static, якщо метод екземпляра decrement()є неправильним, навіть якщо його не було static increment(), оскільки два потоки можуть викликати decrement()різні екземпляри, одночасно змінюючи один і той же лічильник.
Холгер

1
Це, мабуть, вагома причина віддавати перевагу використанню synchronizedблоків загалом (навіть для всього вмісту методу) замість використання модифікатора методу, тобто synchronized(this) { ... }і synchronized(ThreadSafeClass.class) { ... }.
Бруно

++і --не є атомними, навіть наvolatile int . Synchronizedопікується проблемою читання / оновлення / запису з ++/ --, але staticключове слово тут є ключовим . Хороша відповідь!
Кріс Сірефіс,

Повторно, модифікація [статичного поля] з ... методу синхронізованого екземпляра є неправильною : немає нічого по суті неправильного у доступі до статичної змінної зсередини методу екземпляра, і немає нічого по суті неправильного в доступі до неї з synchronizedметоду екземпляра. Тільки не очікуйте, що "синхронізовано" у методі екземпляра забезпечить захист статичних даних. Єдина проблема тут полягає в тому, що ви сказали у своєму першому абзаці: методи використовують різні блокування, намагаючись захистити одні й ті самі дані, і це, звичайно, не забезпечує ніякого захисту.
Соломон Повільний

23

У вас є два синхронізованих методи, але один із них є статичним, а інший - ні. При доступі до синхронізованого методу на основі його типу (статичного чи нестатичного) інший об'єкт буде заблоковано. Для статичного методу блокування буде поставлено на об'єкт Class, тоді як для нестатичного блоку блокування буде встановлено на екземплярі класу, який запускає метод. Оскільки у вас є два різні заблоковані об'єкти, ви можете мати два потоки, які одночасно модифікують один і той же об'єкт.


14

Хто-небудь може пояснити, чому вищезгаданий клас не є безпечним для потоків?

  • increment будучи статичним, синхронізація буде виконуватися для самого класу.
  • decrementбудучи не статичним, синхронізація буде виконуватися на екземплярі об'єкта, але це не забезпечує нічого, що countє статичним.

Я хотів би додати, що для оголошення потокобезпечного лічильника я вважаю, що найпростішим способом є використання AtomicIntegerзамість примітивного int.

Дозвольте мені перенаправити вас на інформацію про java.util.concurrent.atomicпакет.


7

Відповіді інших досить добре пояснили причину. Я просто додаю щось, щоб резюмувати 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синхронізується на рівні об’єкта екземпляра. synchronizedon fun4синхронізується на рівні об’єкта класу. Що означає:

  1. Коли a1.fun1()одночасно дзвонять 2 потоки , останній дзвінок буде заблокований.
  2. Коли виклик потоку 1 a1.fun1()і потік 2 дзвінка a1.fun2()одночасно, останній виклик буде заблокований.
  3. Коли виклик потоку 1 a1.fun1()і виклик потоку 2 a1.fun3()одночасно, без блокування, 2 методи будуть виконуватися одночасно.
  4. Коли виклик потоку 1 A.fun4(), якщо викликають інші потоки A.fun4()або A.fun5()одночасно, останні виклики будуть заблоковані, оскільки synchronizedon fun4є рівнем класу.
  5. Коли виклик потоку 1, виклик A.fun4()потоку 2 a1.fun1()одночасно, без блокування, 2 методи будуть виконуватися одночасно.

6
  1. decrementблокує щось інше, щоб incrementвони не заважали одне одному бігати.
  2. Виклик decrementодного екземпляра блокує іншу річ, ніж виклик decrementіншого, але вони впливають на те саме.

Перше означає , що перекриття викликів incrementі decrementможе привести до скасування відмови (правильно), приріст або декремент.

Другий означає, що два перекриваються дзвінки decrementна різні екземпляри можуть призвести до подвійного зменшення (правильного) або одного зменшення.


4

Оскільки два різні методи, один - рівень екземпляра, а інший - рівня класу, тому вам потрібно зафіксувати 2 різних об’єкти, щоб зробити його ThreadSafe


1

Як пояснюється в інших відповідях, ваш код не є безпечним для потоку, оскільки статичний метод increment()блокує монітор класу, а нестатичний метод decrement()блокує монітор об'єктів.

У цьому прикладі коду існує краще рішення без synchronzedвикористання ключових слів. Вам потрібно використовувати AtomicInteger, щоб забезпечити безпеку потоків .

Нитка безпечна за допомогою 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();
    }

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.