为啥这个类不是线程安全的?

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 方法不是静态的,将在用于调用它的实例上同步。即,它们将在不同的对象上同步,因此两个不同的线程可以同时执行这些方法。由于++-- 操作不是原子的,所以类不是线程安全的。

另外,由于countstatic,因此从同步的instance 方法decrement 修改它是不安全的,因为它可以在不同的实例上调用并同时修改count方式。

【讨论】:

你可以补充,因为countstatic,所以有一个实例方法decrement()是错误的,即使没有staticincrement()方法,因为两个线程可以调用@ 987654337@ 在同时修改同一个计数器的不同实例上。 这可能是一个很好的理由,通常更喜欢使用synchronized 块(甚至在整个方法内容上)而不是在方法上使用修饰符,即synchronized(this) ... synchronized(ThreadSafeClass.class) ... 。跨度> ++ and -- are not atomic, even on volatile intSynchronized 处理 ++/-- 的读/更新/写问题,但 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();

fun1fun2 上的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】:
    decrementincrement 锁定不同的事物,因此它们不会阻止彼此运行。 在一个实例上调用 decrement 与在另一个实例上调用 decrement 锁定不同的事物,但它们影响的是同一事物。

第一个意味着对incrementdecrement 的重叠调用可能导致取消(正确)、增加或减少。

第二个意味着在不同实例上对decrement 的两次重叠调用可能导致双倍递减(正确)或单次递减。

【讨论】:

【参考方案6】:

由于两种不同的方法,一种是实例级别,另一种是类级别,因此您需要锁定 2 个不同的对象以使其成为 ThreadSafe

【讨论】:

【参考方案7】:

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

【讨论】:

以上是关于为啥这个类不是线程安全的?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 ReadOnlyDictionary 不是线程安全的?

SimpleDateFormat类到底为啥不是线程安全的?(附六种解决方案,建议收藏)

高并发SimpleDateFormat类到底为啥不是线程安全的?(附六种解决方案,建议收藏)

高并发SimpleDateFormat类到底为啥不是线程安全的?(附六种解决方案,建议收藏)

Go Map 为啥是非线程安全的?

为啥 Servlet 不是线程安全的? [复制]