为什么该类不是线程安全的?


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#知识。
李慧夏


我也不太了解Java,但是要同步对静态变量的访问,synchronized应该仅在静态方法中使用。因此,在我看来,即使您删除该increment方法,它仍然也不是线程安全的,因为两个实例(只能通过同一实例进行同步访问)可以同时调用该方法。
Onur,2015年

4
只要您从不创建该类的实例,它就是线程安全的。
本杰明·格林鲍姆

1
您为什么认为它不是线程安全的。
拉德瓦尔德

Answers:


134

由于increment方法是static,它将在类对象上同步ThreadSafeClass。该decrement方法不是静态的,并且将在用于调用它的实例上同步。即,它们将在不同的对象上同步,因此两个不同的线程可以同时执行这些方法。由于++--操作不是原子操作,因此该类不是线程安全的。

同样,由于countis是static,因此从decrement同步实例方法修改它是不安全的,因为它可以在不同的实例上调用count并以这种方式并发修改。


12
您可能还会添加一个实例方法,因为它count是is错误的,即使没有方法也是如此,因为两个线程可以在不同的实例上调用并发修改同一计数器。staticdecrement()static increment()decrement()
Holger 2015年

1
这也许是一个很好的理由,synchronized而不是在方法上使用修饰符(即synchronized(this) { ... }and ),一般还是使用块(即使在整个方法的内容上)synchronized(ThreadSafeClass.class) { ... }
布鲁诺

++并且--不是原子的,即使在volatile intSynchronized有需要的读/更新/写问题的护理++/ --,但static关键词是,嗯,关键在这里。好答案!
克里斯·西里菲斯

重新,从...中修改[静态字段] ...同步的实例方法是错误的:从实例方法内部访问静态变量没有内在的错误,从synchronized实例方法访问它也没有内在的错误。只是不要期望实例方法上的“同步”为静态数据提供保护。唯一的问题是您在第一段中所说的:方法使用不同的锁来尝试保护相同的数据,而这些锁当然根本不提供保护。
所罗门慢

23

您有两种同步方法,但其中一种是静态方法,而另一种则不是。访问同步方法时,根据其类型(静态或非静态),将锁定另一个对象。对于静态方法,将在Class对象上放置一个锁,而对于非静态块,将在运行该方法的类的实例上放置一个锁。因为您有两个不同的锁定对象,所以可以有两个线程同时修改同一对象。


14

谁能解释为什么上面的类不是线程安全的?

  • increment 如果是静态的,则同步将在类本身上完成。
  • decrement如果不是静态的,则将在对象实例化上进行同步,但这并不能像count静态的那样保护任何东西。

我想补充一点,以声明一个线程安全计数器,我相信最简单的方法是使用AtomicInteger而不是原始int。

让我将您重定向到java.util.concurrent.atomicpackage-info。


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();

synchronizedfun1fun2在实例对象级别是同步的。synchronizedon fun4在类对象级别同步。意思是:

  1. 当两个线程同时调用a1.fun1()时,后面的调用将被阻止。
  2. 当线程1 a1.fun1()和线程2同时调用a1.fun2()时,后面的调用将被阻止。
  3. 当线程1 a1.fun1()和线程2 a1.fun3()同时调用而没有阻塞时,这两种方法将同时执行。
  4. 当线程1个呼叫A.fun4(),如果其他线程调用A.fun4()或者A.fun5()在同一时间,后者的呼叫将被阻塞,因为synchronizedfun4是类级别。
  5. 当线程1 A.fun4(),线程2同时调用a1.fun1(),无阻塞时,这两种方法将同时执行。

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();
    }

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.