56

volatile在我有两个线程读取/写入它并且不希望取出锁的开销(或潜在的死锁风险)的情况下,我偶尔会使用实例变量;例如,一个计时器线程定期更新一个 int ID,该 ID 作为某个类的 getter 公开:

public class MyClass {
  private volatile int id;

  public MyClass() {
    ScheduledExecutorService execService = Executors.newScheduledThreadPool(1);
    execService.scheduleAtFixedRate(new Runnable() {
      public void run() {
        ++id;
      }
    }, 0L, 30L, TimeUnit.SECONDS);
  }

  public int getId() {
    return id;
  }
}

我的问题:鉴于 JLS 只保证 32 位读取是原子的,那么使用 volatile long 有什么意义?(即 64 位)。

警告:请不要回复说使用volatileoversynchronized是预优化的情况;我很清楚如何/何时使用synchronized,但在某些情况下volatile更可取。例如,在定义用于单线程应用程序的 Spring bean 时,我倾向于使用volatile实例变量,因为不能保证 Spring 上下文会在主线程中初始化每个 bean 的属性。

4

3 回答 3

131

不确定我是否正确理解了您的问题,但JLS 8.3.1.4。易失性字段状态:

一个字段可能被声明为 volatile,在这种情况下,Java 内存模型确保所有线程都能看到变量的一致值(第 17.4 节)。

而且,也许更重要的是,JLS 17.7 非原子处理 double 和 long

17.7 double 和 long 的非原子处理
[...]
出于 Java 编程语言内存模型的目的,对非易失性 long 或 double 值的单次写入被视为两次单独的写入:每个 32 位写入一次一半。这可能会导致线程从一次写入中看到 64 位值的前 32 位,而从另一次写入中看到后 32 位。volatile long 和 double 值的写入和读取始终是原子的。对引用的写入和读取始终是原子的,无论它们是作为 32 位还是 64 位值实现的。

也就是说,“整个”变量受到 volatile 修饰符的保护,而不仅仅是两个部分。这诱使我声称对 s使用 volatile比对 s使用更重要,因为对于非易失性 long/double而言,甚至读取都不是原子的。longint

于 2010-06-14T14:54:00.903 回答
15

This can be demonstrated by example

  • constantly toggle two fields, one marked volatile and one not between all bits set and all bits clear
  • read the field values on another thread
  • see that the foo field (not protected with volatile) can be read in an inconsistent state, this never happens to the bar field protected with volatile

Code

public class VolatileTest {
    private long foo;
    private volatile long bar;
    private static final long A = 0xffffffffffffffffl;
    private static final long B = 0;
    private int clock;
    public VolatileTest() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    foo = clock % 2 == 0 ? A : B;
                    bar = clock % 2 == 0 ? A : B;
                    clock++;
                }
            }

        }).start();
        while (true) {
            long fooRead = foo;
            if (fooRead != A && fooRead != B) {
                System.err.println("foo incomplete write " + Long.toHexString(fooRead));
            }
            long barRead = bar;
            if (barRead != A && barRead != B) {
                System.err.println("bar incomplete write " + Long.toHexString(barRead));
            }
        }
    }

    public static void main(String[] args) {
        new VolatileTest();
    }
}

Output

foo incomplete write ffffffff00000000
foo incomplete write ffffffff00000000
foo incomplete write ffffffff
foo incomplete write ffffffff00000000

Note this only happens for me when running on a 32 bit VM, on 64 bit VM I couldn't get a single error in several minutes.

于 2015-12-22T21:23:38.920 回答
6

“volatile”有多种用途:

  • 保证原子写入双/长
  • 保证当线程 A 看到线程 B 对 volatile 变量所做的更改时,线程 A 还可以看到线程 B 在更改为 volatile 变量之前所做的所有其他更改(考虑在设置单元格本身之后设置数组中使用的单元格的数量) .
  • 基于只有一个线程可以更改变量的假设来防止编译器优化(想想紧密循环while (l != 0) {}

还有更多吗?

于 2015-12-22T21:34:48.340 回答