静态变量的线程安全性

18
class ABC implements Runnable {
    private static int a;
    private static int b;
    public void run() {
    }
}

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

因此,每个线程都会递增这两个变量并将它们放入Hashtable中。我该如何使这些操作线程安全?


@Frozen Spider @Bohemian @Nim 在Java SE 7程序员考试804书中,有人能解释一下这段代码吗?public void run() { synchronized(SharedCounter.class) { SharedCounter.count++; } } 然而,这段代码效率低下,因为它每次都要获取和释放锁来增加计数器的值。 - shareef
@shareef 评论不是用来提问的。相反,提出新问题 - Bohemian
@Bohemian,这是我的问题:http://stackoverflow.com/questions/25463440/thread-safety-static-variables - shareef
4个回答

12

我会使用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

9

取决于需要保证线程安全的内容。对于这些int原始类型,您需要将它们替换为AtomicInteger或仅在synchronized方法或块中操作。如果您需要使跨线程的Hashtable线程安全,则无需执行任何操作,因为它已经是同步的。


将一个整数声明为volatile并不能使增量操作成为原子操作。但是,它确实可以使用AtomicIntegerFieldUpdater。 - Michael Borgwardt
谢谢,看来我自己对volatile的理解有误。已经更正了答案。 - Alex Abdugafarov
换句话说,静态字段不是线程安全的。只有 final 字段是线程安全的。对于既不是 final 的实例字段和静态字段,你必须在同步方法/语句中访问它们或使用 Java 7/8 中提供的并发锁功能。 - JohnMerlino
1
不,那不是完全正确的想法。静态或final与线程安全无关。当然,final int将是线程安全的,因为它是不可变的 - 仅仅将可变对象(例如ArrayList)设置为final并不能神奇地使其线程安全。即使将其getter同步也没有帮助。您必须代理和同步所有实例希望公共方法,而不提供对实例本身的访问。 - Alex Abdugafarov

6
使用同步方法,例如: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
  }
}

注意:这些方法假设您想以一个原子操作增加ab的值,其他答案强调了如何使用原子操作单独增加它们的值。

需要在类级别而不是对象级别上同步,这样不是更必要吗? - mcfinnigan
1
@mcfinnigan - 没错,我假设访问是通过单个实例进行的... - Nim
如果需要,您也可以直接针对类对象进行同步:synchronize(ABC.class) { ... } - mcfinnigan
@mcfinnigan - 我已经有一段时间没碰 Java 了,所以有点生疏! :) - Nim
哈哈,别担心。我每天都用它,但我也经常忘记基础知识! - mcfinnigan

3
我想在此回答中添加一些细节。
首先,OP正在询问以下问题:
如何使这些操作线程安全?
在回答这个问题之前,我们需要就什么是"线程安全"达成一致。我最喜欢的定义来自《Java并发实践》:
如果一个类在多线程访问时表现正确,无论线程执行的调度或交错方式如何,并且在调用代码方面没有额外的同步或其他协调,则该类是线程安全的。
如果您同意这个定义,那么我们在进一步讨论之前已经达成了一致。现在让我们回到你提到的操作。它们是a++和b++,在递增之后,你将把它们放入一个哈希表中。
对于a++操作,它实际上不是单个操作。它遵循“读取修改写入”的模型。正如你所看到的,它实际上包含三个独立的步骤。读取值,加一,然后保存回去。如果你有两个线程同时读取变量a,它的值为1,在修改和保存操作之后,值将为2,但实际上应该是3。为避免出现这种情况,可以像其他人建议的那样,使用AtomicInteger而不是直接使用int类型。AtomicInteger将保证某些操作(如递增)以原子方式执行。也就是说,“读取修改写入”操作不能被分割,并且将作为一个独立的步骤执行。之后,OP想将该值保存到哈希表中。哈希表是一个线程安全的容器,无需其他同步。
希望这个澄清能帮助其他人。谢谢。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接