为啥这个类不是线程安全的?
Posted
技术标签:
【中文标题】为啥这个类不是线程安全的?【英文标题】:Why is this class not thread safe?为什么这个类不是线程安全的? 【发布时间】:2015-07-24 08:51:20 【问题描述】:class ThreadSafeClass extends Thread
private static int count = 0;
public synchronized static void increment()
count++;
public synchronized void decrement()
count--;
谁能解释为什么上面的类不是线程安全的?
【问题讨论】:
我不了解 Java,但看起来这些方法中的每一个都是单独线程安全的,但是您可以在 each 的方法同时进行。也许如果您有一个采用 bool (increment
) 的方法,那么它将是线程安全的。或者,如果您使用了一些锁定对象。正如我所说,我不了解 Java - 我的评论源于 C# 知识。
见***.com/questions/7805192/…
我也不是很了解Java,但是要同步访问静态变量,synchronized
应该只用在静态方法中。所以在我看来,即使你删除了increment
方法,它仍然不是线程安全的,因为两个实例(只能通过同一个实例进行同步访问)可以同时调用该方法。
只要你从不创建类的实例,它就是线程安全的。
为什么你认为它不是线程安全的。
【参考方案1】:
由于increment
方法是static
,它将在ThreadSafeClass
的类对象上同步。 decrement
方法不是静态的,将在用于调用它的实例上同步。即,它们将在不同的对象上同步,因此两个不同的线程可以同时执行这些方法。由于++
和--
操作不是原子的,所以类不是线程安全的。
另外,由于count
是static
,因此从同步的instance 方法decrement
修改它是不安全的,因为它可以在不同的实例上调用并同时修改count
方式。
【讨论】:
你可以补充,因为count
是static
,所以有一个实例方法decrement()
是错误的,即使没有static
increment()
方法,因为两个线程可以调用@ 987654337@ 在同时修改同一个计数器的不同实例上。
这可能是一个很好的理由,通常更喜欢使用synchronized
块(甚至在整个方法内容上)而不是在方法上使用修饰符,即synchronized(this) ...
和synchronized(ThreadSafeClass.class) ...
。跨度>
++
and --
are not atomic, even on volatile int
。 Synchronized
处理 ++
/--
的读/更新/写问题,但 static
关键字在这里是 key。好答案!
Re, modifying [a static field] from...一个同步的实例方法是错误的:从实例方法中访问静态变量本身并没有错,从synchronized
实例方法访问它也没有本质上的错误。只是不要指望实例方法上的“同步”来为静态数据提供保护。这里唯一的问题是您在第一段中所说的:这些方法使用不同的锁来尝试保护相同的数据,而这当然根本没有提供任何保护。【参考方案2】:
正如其他答案中所解释的,您的代码不是线程安全的,因为静态方法 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();
【讨论】:
【参考方案3】:其他人的回答很好解释了原因。我只是添加一些东西来总结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();
fun1
和 fun2
上的synchronized
在实例对象级别同步。 fun4
上的 synchronized
在类对象级别上同步。这意味着:
-
当2个线程同时调用
a1.fun1()
时,后面的调用会被阻塞。
当线程1调用a1.fun1()
和线程2同时调用a1.fun2()
时,后面的调用会被阻塞。
当线程1调用a1.fun1()
和线程2同时调用a1.fun3()
时,不阻塞,2个方法会同时执行。
当线程1调用A.fun4()
时,如果其他线程同时调用A.fun4()
或A.fun5()
,后面的调用将被阻塞,因为fun4
上的synchronized
是类级别的。
线程1调用A.fun4()
,线程2同时调用a1.fun1()
,不阻塞,2个方法同时执行。
【讨论】:
【参考方案4】:谁能解释为什么上面的类不是线程安全的?
increment
是静态的,同步将在类本身上完成。
decrement
不是静态的,将在对象实例化时完成同步,但这并不能确保任何事情,因为 count
是静态的。
我想添加它来声明一个线程安全计数器,我相信最简单的方法是使用 AtomicInteger
而不是原始 int。
让我将您重定向到java.util.concurrent.atomic
package-info。
【讨论】:
【参考方案5】:decrement
与 increment
锁定不同的事物,因此它们不会阻止彼此运行。
在一个实例上调用 decrement
与在另一个实例上调用 decrement
锁定不同的事物,但它们影响的是同一事物。
第一个意味着对increment
和decrement
的重叠调用可能导致取消(正确)、增加或减少。
第二个意味着在不同实例上对decrement
的两次重叠调用可能导致双倍递减(正确)或单次递减。
【讨论】:
【参考方案6】:由于两种不同的方法,一种是实例级别,另一种是类级别,因此您需要锁定 2 个不同的对象以使其成为 ThreadSafe
【讨论】:
【参考方案7】:您有两个同步方法,但其中一个是静态的,另一个不是。访问同步方法时,根据其类型(静态或非静态),将锁定不同的对象。对于静态方法,将对 Class 对象加锁,而对于非静态块,将对运行该方法的类的实例加锁。因为你有两个不同的锁定对象,你可以有两个线程同时修改同一个对象。
【讨论】:
以上是关于为啥这个类不是线程安全的?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 ReadOnlyDictionary 不是线程安全的?
SimpleDateFormat类到底为啥不是线程安全的?(附六种解决方案,建议收藏)
高并发SimpleDateFormat类到底为啥不是线程安全的?(附六种解决方案,建议收藏)