方法中是否需要“易变”?

时间:2012-03-10 21:25:44

标签: java multithreading thread-safety

我已经读过,当一个线程访问一个变量以确保线程看到正确的值时你应该使用volatile,但是当在一个方法中使用该变量时这也适用吗?

示例代码:

public class Limiter
{
    int max = 0;

    public synchronized void doSomething()
    {
         max++;
         if(max < 10)
             System.out.println("Work");

         System.out.println(max);
    }

}

假设如果多线程调用doSomething,那么max是否会被设置为与前一个Thread调用方法时相同的状态?

因为doSomething()是同步的,我知道只有一个线程可以修改max,但是当下一个线程调用它时会发生什么? max可以是不同的值,因为它不使用volatile吗?或者它是否安全,因为“限制器”实例会自行修改它?

2 个答案:

答案 0 :(得分:7)

volatile是字段的声明的一部分,而不是 use 的一部分。将局部变量声明为volatile是没有意义的,因为不同的线程不会看到局部变量。

在您的情况下,只要非同步方法中的代码没有访问max,您就可以了 - 内存模型基本上确保只要所有代码获取/释放相同的监视器“保护”变量,所有线程都会看到一致的值序列。 (除了其他任何事情,每个线程在访问值之前必须获取监视器这一事实意味着一次只能有一个线程能够访问该值 - 你可以编写一个总排序“线程X让监视器在时间t0-t1,线程Y在时间t4-t5“等时有监视器”

答案 1 :(得分:2)

  

我已经读过,当一个线程访问一个变量以确保线程看到正确的值时你应该使用volatile,但是当在一个方法中使用该变量时这也适用吗?

这在许多方面都是不准确的 *

首先:volatile只能在字段上使用。它不能用于其他类型的变量;即局部变量或参数。 (原因:它没有意义,因为其他类型的变量只能在一个线程中看到。)

其次:volatile语义适用于您是否从方法调用。

第三:volatile是使用synchronized方法(或块)的替代。但正确使用synchronized通常以使用同步构造执行的所有访问和更新为条件。任何非同步访问或更新都可能使程序(整体)不正确。

最后:volatile表示对字段的访问将看到字段值的最新值。这不足以保证正确性。例如:

public Counter {
    private volatile int count;

    public void increment() {
        // This is NOT correct.
        count++;
    }
}

上述示例不正确的原因是count++不是原子的。它实际上意味着(字面意思)count = count + 1,另一个线程可以在访问它的当前线程和写回更新值之间更改count。这会导致偶尔丢失增量。

很容易看出,将max声明为volatile并删除synchronized关键字的代码版本出于完全相同的原因会出错。在这方面,您当前的版本更正确。但是,它仍然不完全正确,因为没有什么能阻止其他类(在同一个包中)访问或更新max。如果这是在另一个线程中完成的,那么除了漏洞抽象之外,你还有潜在的并发问题。


所以,回答你的问题:

  

假设如果一个多线程调用doSomething,那么max是否安全,那么max将被设置为与前一个Thread调用方法时相同的状态?

并非完全,因为抽象漏洞。 (声明max是私有修复此问题。)

  

因为doSomething()是同步的,我知道只有一个线程可以修改max,但是当下一个线程调用它时会发生什么?

synchronized块确保在同一对象上同步的下一个线程将看到这些更新。 (如何实现这一点是特定于平台的......)

  

最大可以是不同的值,因为它不使用volatile吗?

不...模糊漏洞抽象问题。

  

或者它是否安全,因为“限制器”实例会自行修改它?

该事实与该代码的线程安全性/正确性无关。重要的是同步,而不是自我修改。


*至少,您对所阅读内容的理解总结是不准确的。据我们所知,您阅读的原始文本可能是正确的。


<强>更新

  

我读到的部分是讨论字段(如线程可访问的变量)。对困惑感到抱歉。我想知道的是它是否也适用于线程调用的对象中方法使用的所有变量。

这适用于同步区域覆盖线程读取和/或写入的对象的所有字段。实际上,它适用于其他对象的字段,以及在该区域中读/写的数组元素。 (对于非字段的变量,这是“没有实际意义”,因为没有其他线程可以看到它们。)

但是,需要注意的是,它仅适用于同步点。如果第二个线程未正确同步,则所有投注均已关闭。