为什么这段代码线程安全?

时间:2016-01-16 21:44:01

标签: java multithreading

我正在准备OCP考试,我在模拟考试中发现了这个问题:

假设:

class Calculator {
    private AtomicInteger i = new AtomicInteger();
    public void add(int value) {
        int oldValue = i.get();
        int newValue = oldValue + value;
        System.out.print(i.compareAndSet(oldValue,newValue));
    }
    public int getValue() {
        return i.get();
    }
}

你能做些什么来使这个类线程安全?

令人惊讶的是,对我来说,答案是: "类计算器是线程安全的"

一定是我没有正确理解这个概念。据我所知,当所有方法在线程并发下按预期工作时,类是线程安全的。现在,如果两个线程同时调用getValue(),则调用add()传递一个不同的值,然后再次调用getValue(),' second'线程不会看到它的价值增加了​​。

我理解oldValue和newValue作为局部变量存储在方法堆栈中,但这并不能防止对compareAndSet的第二次调用会发现oldValue不是当前值,因此赢得了#39 ; t添加newValue。

我在这里缺少什么?

4 个答案:

答案 0 :(得分:3)

根据JCIP

  
    

如果一个类在从多个线程访问时行为正确,则无论调度如何,该类都是线程安全的     由运行时环境交错执行这些线程,没有额外的同步或     调用代码的其他协调。

  

虽然没有线程安全的定义,也没有类的规范,但在我看来,add类中Calculator方法的任何理智定义都是“正确的”在任何情况下,AtomicInteger i的值都会增加,“无论执行的调度或交错如何”。

因此,在我看来,这个定义的类不是线程安全的。

答案 1 :(得分:3)

这里的术语“线程安全”显然存在问题,因为它不是绝对的。什么被认为是线程安全取决于您期望程序做什么。在大多数实际应用程序中,您不会认为此代码是线程安全的。

然而,JLS正式指定了一个不同的概念:

  

程序正确同步当且仅当所有顺序执行时   一致的执行没有数据竞争。

     

如果一个程序正确同步,那么所有的执行都是   程序似乎是顺序一致的

正确同步是一个精确定义的客观条件,根据该定义,代码正确同步,因为every access to i is in a happens-before relationship with every other access,这足以满足标准。

读/写的确切顺序取决于不可预测的时序这一事实是一个不同的问题,在正确同步的范围之外(但在大多数人称之为线程安全的范围内)。

答案 2 :(得分:2)

add方法将执行以下两项操作之一:

  • value添加到i的值,然后打印true
  • 不执行任何操作并打印false

我所看到的线程安全的理论上声音 1 的定义是这样的:

  

给定一组要求,如果程序在多线程环境中针对所有可能执行的要求更正,那么程序就这些要求而言是

在这种情况下,我们没有明确的要求声明,但如果我们推断预期的行为如上所述,那么该类就线程安全而言相对于推断要求。

现在,如果要求add 总是value添加到i,则该实施不符合要求。在这种情况下,您可以认为该方法不是线程安全的。 (在单线程用例add中始终有效,但在多线程用例中,add方法偶尔可能无法满足要求...因此将是非线程安全的。)

1 - 相比之下,维基百科的描述(见于2016-01-17)是这样的:"如果一段代码只操纵一个代码中的共享数据结构,它就是线程安全的。保证多个线程同时安全执行的方式。" 问题在于它没有说出什么"安全执行"手段。 Eric Lippert的2009年博文"What is this thing you call thread-safe"是非常恰当的。

答案 3 :(得分:0)

它是线程安全的,因为compareAndSet是线程安全的,而且它是修改对象中共享资源的唯一部分。

有多少线程同时进入该方法体并没有什么区别。第一个到达结尾并调用compareAndSet“wins”并获取更改值,而其他人发现值已更改,并且compareAndSet返回false。它永远不会导致系统处于不确定状态,尽管调用者可能不得不在任何实际场景中处理错误结果。