1

这个 Java 类线程安全还是重置方法也需要同步?如果是,有人可以告诉我原因吗?

public class NamedCounter {
   private int count;
   public synchronized void increment() { count++; }
   public synchronized int getCount() { return count; }
   public void reset() { count = 0; }
}
4

5 回答 5

8

并非没有同步 rest() 并添加更多方法。您将遇到需要更多方法的情况。例如

NamedCounter counter = new NamedCounter();
counter.increment();
// at this exact time (before reaching the below line) another thread might change changed the value of counter!!!!
if(counter.getCount() == 1) {
    //do something....this is not thread safe since you depeneded on a value that might have been changed by another thread
}

要解决上述问题,您需要类似的东西

NamedCounter counter = new NamedCounter();
if(counter.incrementAndGet()== 1) { //incrementAndGet() must be a synchronized method
    //do something....now it is thread safe
}

相反,请使用涵盖所有情况的 Java 内置类 AtomicInteger。或者,如果您正在尝试学习线程安全,则使用 AtomicInteger 作为标准(从中学习)。

对于生产代码,使用 AtomicInteger 无需三思而后行!请注意,使用 AtomicInteger 不会自动保证代码中的线程安全。您必须使用 api 提供的方法。他们在那里是有原因的。

于 2013-07-04T08:36:39.687 回答
2

请注意,这synchronized不仅仅是关于互斥,它从根本上是关于根据操作的可见性对操作进行正确排序。因此reset也必须同步,否则它所做的写入可能会同时发生在其他两个方法中,并且无法保证可见。

总而言之,您的类不是线程安全的,但只要您同步该reset方法,它就会是线程安全的。

于 2013-07-04T08:33:51.033 回答
1

您还必须同步您的reset()方法。

为了使class线程安全,您必须同步访问变量的所有路径,否则您将与未同步的路径产生不希望的结果。

于 2013-07-04T08:36:42.210 回答
0

您还需要将同步添加到重置方法,然后它将被同步。但是通过这种方式,您可以通过锁实现同步,也就是说,访问该方法的每个线程都会锁定 NamedCounter 对象实例。

但是,如果使用 AtomicInteger 作为计数变量,则不再需要同步,因为它使用 CAS cpu 操作来实现原子性而无需同步。

于 2013-07-04T08:34:47.170 回答
0

不是答案,但评论太长了:

如果 reset() 已同步,则 0 对稍后读取或递增计数器的任何线程都是可见的。没有同步,就没有可见性保证。查看并发增量和非同步重置的交互,可能是在进入方法之前,增量线程可见 0,然后结果将为 1。如果在增量的读取和写入之间将计数器设置为 0,则重置将被遗忘。如果在写入后设置,则最终结果将为 0。因此,如果要断言对于每个读取线程,复位后计数器为 0,则该方法也必须同步。但是 David Schwartz 是正确的,如果没有这些交互的高级语义,这些低级同步毫无意义。

于 2013-07-04T11:40:21.963 回答