如何理解"变量在使用volatile关键字时不参与与其他状态变量的不变量"?

xin*_*ang 11 java concurrency volatile

从"实践中的Java并发"一书第26页:

仅当满足以下所有条件时,才能使用volatile变量:

  • 对变量的写入不依赖于其当前值,或者您可以确保只有一个线程更新该值;

  • 变量不参与其他状态变量的不变量; 和 

  • 在访问变量时,出于任何其他原因,不需要锁定.

如何理解" 变量在使用volatile关键字时不参与与其他状态变量的不变量 "?

Adr*_*tti 19

"不变量"的简单定义:在对象的生命周期中始终为真的条件.

易失性变量不共享synchronized块的原子性特征.

这就是为什么你不能在具有与多个变量相关的不变量的类中使用它们.

例如,假设你有一个class模型由两个变量描述的时间间隔:startend.不变条件可能start总是小于或等于end.如果两个变量(仅作为示例)被声明为volatile,那么您可以依赖于可见性功能volatile但是您无法确定在涉及两个变量的更改期间始终满足不变量.认为:

public void setInterval(Date newStart, Date newEnd)
{
 // Check if inputs are correct

 // Here the object state is valid
 start = newStart;

 // If another thread accesses this object now it will
 // see an invalid state because start could be greater than end

 end = newEnd;
 // Here the object state is valid again
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您可以确保每个线程都可以看到更改,但在两个指令的中间,对象状态可能无效.因为它可以被其他线程访问(记住这是一个简单的情况,所以它可能但不太可能)然后可以打破不变条件"start <end".

这就是为什么在一组(小)明确定义的模式之外,某种方式不鼓励使用volatile.只有在满足以下条件时才应使用volatile变量:

  • 该变量不涉及与其他变量相关的不变量(由于上面解释的原因).
  • 写入变量的值不依赖于其当前值.

例如,表达式int a = i++;不是原子的,严格来说它不是线程安全的,因为它将被重写为这样的东西:

int temp = i;
i = i + 1;
int a = temp;
Run Code Online (Sandbox Code Playgroud)

要从线程的角度来看它是原子的,你可以想象一个像这样的类:

public class MyAtomicInteger
{
  public synchronized increment()
  {
    x = x + 1;
  }

  private int x;
}
Run Code Online (Sandbox Code Playgroud)

当然它存在一个真正的实现,AtomicInteger它是java.util.concurrent.atomic包的一部分,它为无锁并发编程提供了一些简单的基本例程.