代码之家  ›  专栏  ›  技术社区  ›  Adamski

使用易挥发的长型有什么意义吗?

  •  53
  • Adamski  · 技术社区  · 15 年前

    我偶尔使用 volatile 实例变量,如果我有两个线程在读/写它,并且不希望取出锁的开销(或潜在的死锁风险);例如,计时器线程定期更新某个类上作为getter公开的in t id:

    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位读取是原子的,那么在 曾经 使用易挥发的长?(即64位)。

    告诫 :请不要回复说使用 不稳定的 结束 synchronized 是一个预先优化的案例;我很清楚如何/何时使用 同步的 但有些情况下 不稳定的 更好。例如,在定义用于单线程应用程序的SpringBean时,我倾向于 不稳定的 实例变量,因为不能保证Spring上下文将在主线程中初始化每个bean的属性。

    3 回复  |  直到 9 年前
        1
  •  121
  •   reevesy onejigtwojig    12 年前

    不确定我是否正确理解你的问题,但是 JLS 8.3.1.4. volatile Fields 国家:

    字段可以声明为易失性,在这种情况下,Java内存模型确保所有线程都能看到变量的一致值。 §17.4 )

    更重要的是, JLS 17.7 Non-atomic Treatment of double and long :

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

    也就是说,“整个”变量受volatile修饰符保护,而不仅仅是这两部分。这让我觉得 更重要的是 使用volatile long 比它的用途 int 自此以来 连读书都没有 是非挥发性长/双的原子。

        2
  •  9
  •   Adam    9 年前

    这可以通过例子来证明。

    • 不断地切换两个字段,一个标记为易失性,一个不在所有位设置和所有位清除之间。
    • 读取其他线程上的字段值
    • 请注意,可以在不一致的状态下读取foo字段(不受volatile保护),这不会发生在受volatile保护的bar字段上。

    代码

    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();
        }
    }
    

    产量

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

    注意,只有在32位虚拟机上运行时才会发生这种情况,在64位虚拟机上,我在几分钟内无法获得单个错误。

        3
  •  3
  •   Arkadiy    9 年前

    “挥发性”有多种用途:

    • 保证原子写入到double/long
    • 确保当线程A看到线程B所做的可变变量的变化时,线程A也可以看到线程B在变为可变变量之前所做的所有其他变化(考虑在设置单元本身之后在数组中设置已用单元的数目)。
    • 基于只有一个线程可以更改变量(think tight loop)的假设阻止编译器优化 while (l != 0) {} .

    还有吗?