【问题标题】:Thread safety for static variables静态变量的线程安全
【发布时间】:2011-11-18 13:04:19
【问题描述】:
class ABC implements Runnable {
    private static int a;
    private static int b;
    public void run() {
    }
}

我有一个如上所述的 Java 类。我有这个类的多个线程。在run() 方法中,变量ab 分别递增数次。在每次增量时,我都会将这些变量放入一个 Hashtable 中。

因此,每个线程都会增加这两个变量并将它们放入哈希表中。如何使这些操作线程安全?

【问题讨论】:

  • @Frozen Spider @Bohemian @Nim in book java se 7 程序员考试 804 有人可以向我解释public void run() { synchronized(SharedCounter.class) { SharedCounter.count++; } } 但是,这段代码效率低下,因为它每次都获取和释放锁只是为了增加计数的值。
  • @shareef 评论不是用来提问的。相反,ask a new question
  • @Bohemian 这是我的问题stackoverflow.com/questions/25463440/…

标签: java multithreading thread-safety


【解决方案1】:

我会使用AtomicInteger,它被设计为线程安全的,非常易于使用,并且为应用程序提供了绝对最小的同步开销:

class ABC implements Runnable {
    private static AtomicInteger a;
    private static AtomicInteger b;
    public void run() {
        // effectively a++, but no need for explicit synchronization!
        a.incrementAndGet(); 
    }
}

// In some other thread:

int i = ABC.a.intValue(); // thread-safe without explicit synchronization

【讨论】:

    【解决方案2】:

    取决于什么需要是线程安全的。对于这些int 原语,您需要将它们替换为AtomicInteger 或仅在synchronized 方法或块中使用它们。如果你需要让你的跨线程 Hashtable 线程安全,你不需要做任何事情,因为它已经同步了。

    【讨论】:

    • 使 int volatile 不会使增量操作成为原子操作。它确实可以使用 AtomicIntegerFieldUpdater
    • 谢谢,看来我自己对volatile的理解有误。更正了答案。
    • 换句话说,静态字段不是线程安全的。只有 final 字段是线程安全的。对于非最终的实例和静态字段,您必须在同步方法/语句中访问它们,或者使用 Java 7/8 中提供的并发锁功能。
    • 不,不完全是这个想法。静态或最终与线程安全无关。当然,final int 将是线程安全的,因为它是不可变的——仅仅将可变对象(例如ArrayList)设为final 不会神奇地使其成为线程安全的。使其吸气剂同步也无济于事。您必须代理和同步所有实例一厢情愿的公共方法,而不提供对实例本身的访问权限。
    【解决方案3】:

    使用synchronized 方法,例如

    public synchronized void increment()
    {
      a++; b++;
      // push in to hash table.
    }
    

    如果您通过单个实例访问静态数据,上述方法很好,但是如果您有多个实例,那么您需要在一些静态对象上进行同步 - 例如(未经测试)..

    private static Object lock = new Object();
    

    在方法中

    public void increment()
    {
      synchronize(lock)
      {
        a++;b++;
        // do stuff
      }
    }
    

    注意:这些方法假设您希望在一个 atomic 操作中增加 ab,其他答案强调如何使用它们单独增加原子。

    【讨论】:

    • 不需要在类级别而不是对象级别同步吗?
    • @mcfinnigan - 正确,我假设访问是通过单个实例...
    • 如果需要,也可以直接针对 Class 对象进行同步:synchronize(ABC.class) { ... }
    • @mcfinnigan - 我已经有一段时间没有接触 Java 了,所以有点生疏了! :)
    • 哈哈,不用担心。我每天都用它,但我总是忘记基本的东西!
    【解决方案4】:

    我想补充一些关于这个问题的答案的细节。

    首先,OP在询问:

    我怎样才能使这些操作线程安全?

    在回答这个问题之前,我们需要就什么是线程安全达成一致。我最喜欢的定义来自“Java Concurrency In Practice”,它是

    如果一个类在被访问时行为正确,那么它就是线程安全的 从多个线程,无论调度或 运行时交错执行这些线程 环境,并且没有额外的同步或其他 调用代码部分的协调。

    如果您同意该定义,则意味着我们在进一步讨论之前达到了一致性。让我们回到你的意思,它是a++b++,在增加之后,你将它们放入一个HashTable

    对于a++ 操作,它并不是真正的单一操作。它遵循read modify write的模型。如您所见,它实际上包含三个单独的步骤。读取该值,将其加一并保存回来。如果您有两个线程同时读取变量a 的值为1,则在修改并保存回操作之后。该值将是2,但实际上应该是3。为避免这种情况发生,就像其他人建议的那样,您可以直接使用 AtomicInteger 而不是 int 类型。 AtomicInteger 将保证像增量这样的一些操作以原子方式执行。也就是说,read modify write操作不能分割,将作为一个单独的步骤执行。
    之后,OP 想要将值保存到 HashTable 中。 HashTable 是一个线程安全的容器,不需要其他同步。
    希望这个澄清可以以其他方式帮助某人。谢谢。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-24
      • 1970-01-01
      • 2011-04-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多