实现原子操作,因为java volatile保证发生在关系之前?

时间:2015-12-22 19:18:47

标签: java concurrency

实现原子操作,因为java volatile保证发生在关系之前?

我已经读过关于volatile的发生之前的事情:

如果线程A写入易失性变量而线程B随后读取相同的易变量,那么在写入volatile变量之前,线程A可见的所有变量在读取volatile变量后也将对线程B可见。

现在,我有两个变量:

         static int m_x;
volatile static int m_y;

现在我有两个线程,一个只写入它们,然后先写入m_x,然后写入m_y;另一个只读取它们,首先读取m_y,然后读取m_x。

我的问题是:写操作是原子的吗?读操作是原子吗?

根据我的理解,它们应该是原子的:

(1)在写入线程一侧,在写入(写入1)之后,它不会将其高速缓存刷新到主存储器,因为m_x是非易失性的,因此,读取线程无法看到更新;在(写入-2)之后,它将其缓存刷新到主存储器,因为m_y是易失性的;

(2)在读取线程一侧,在(读取1)上,它将从主存储器更新其缓存,因为m_y是易失性的;在(读取2),它不会从主存储器更新其缓存,因为m_x不是易失性的。

由于上述两个原因,我认为读取线程应该始终观察两个变量的原子值。正确?

public class test {
         static int m_x;
volatile static int m_y;

public static void main(String[] args) {
    // write
    new Thread() {
        public
        void run() {
            while(true) {
                int x = randInt(1, 1000000);
                int y = -x;
                m_x = x; // (Write-1)
                m_y = y; // (Write-2)
            }
        }
    }.start();

    // read
    new Thread() {
        public
        void run() {
            while(true) {
                int y = m_y; // (Read-1)
                int x = m_x; // (Read-2)

                int sum = y + x;
                if (sum != 0) {
                    System.out.println("R:sum=" + sum);
                    System.out.println("R:x=" + x);
                    System.out.println("R:y=" + y);
                    System.out.println("\n");
                }
            }
        }
    }.start();
}

public static int randInt(int Min, int Max) {
    return Min + (int)(Math.random() * ((Max - Min) + 1));
}

}

2 个答案:

答案 0 :(得分:1)

如评论中所述,两次读写都不是原子的。使用volatile关键字无法实现原子性。

运行程序时可以观察到这一事实。

要同时读取/写入两个变量,您需要正确同步或创建自己的不可变值。

稍后再做

public class ConcurrencyTestApp {

    // use volatile for visibility
    private volatile ImmutableValue immutableValue = new ImmutableValue(0, 0); // initial, non-null value

    static class ImmutableValue {
        private final int x;
        private final int y;

        ImmutableValue(final int x, final int y) {
            this.x = x;
            this.y = y;
        }

        int getX() {
            return x;
        }

        int getY() {
            return y;
        }

        @Override
        public String toString() {
            return String.format("x = %s\t| y = %s", x, y);
        }
    }

    void replaceOldWithNewValue(final ImmutableValue newValue) {
        immutableValue = newValue;
    }

    ImmutableValue getImmutableValue() {
        return immutableValue;
    }

    static class Writer extends Thread {

        private final ConcurrencyTestApp app;

        Writer(ConcurrencyTestApp app) {
            this.app = app;
        }

        volatile boolean isRunning = true;

        @Override
        public void run() {
            while (isRunning) {
                int x = randInt(1, 1000000);
                int y = -x;

                app.replaceOldWithNewValue(new ImmutableValue(x, y));
            }

        }

        int randInt(int Min, int Max) {
            return Min + (int) (Math.random() * ((Max - Min) + 1));
        }
    }

    static class Reader extends Thread {
        private final ConcurrencyTestApp app;

        Reader(ConcurrencyTestApp app) {
            this.app = app;
        }

        volatile boolean isRunning = true;

        @Override
        public void run() {
            while (isRunning) {
                ImmutableValue value = app.getImmutableValue();
                System.out.println(value);
                int x = value.getX();
                int y = value.getY();
                int sum = x + y;
                if (sum != 0) {
                    System.out.println("R:sum=" + sum);
                    System.out.println("R:x=" + x);
                    System.out.println("R:y=" + y);
                    System.out.println("\n");
                }
            }
        }
    }

    public static void main(String[] args) {

        ConcurrencyTestApp app = new ConcurrencyTestApp();
        Writer w = new Writer(app);
        Reader r = new Reader(app);

        w.start();
        r.start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        w.isRunning = false;
        r.isRunning = false;
    }

}

为了进一步参考,我推荐Brian Goetz和Tim Peierls撰写的 Java并发实践 一书。

<强>附录

  

...

     

由于上述两个原因,我认为读线程应该始终如此   观察两个变量的原子值。正确?

<强> 错!

......你错过了重要的一部分。

有关参考,请参阅JSR 133 (Java Memory Model) FAQ by Jeremy Manson and Brian Goetz部分 volatile有什么作用?

在你的程序中,没有任何阻止以下内容:

假设int m_x = x1 int m_y = y1

  1. 你的Writer-Thread执行到Write-1
    • int m_x现在设置为值 x2 (您的Reader-Thread可能会显示也可能不会显示)
  2. 你的Writer-Thread的执行被暂停(无论出于何种原因)
  3. 您的Reader-Thread执行Read-1Read-2(没有什么能阻止Reader-Thread执行此操作)
    • int y = m_y仍然 y1 ,因为您的Writer-Thread尚未执行更进一步
    • int x = m_x,可能是 x2 (但它也可能 x1
  4. 您的Reader-Thread被暂停,您的Writer-Thread继续
    • int m_y现已设置为值 y2 (仅限Read-1 y2 Read-2将保证获得 x2 - 除非你的Writer-Thread继续)
  5. ......等等
  6. 要看到自己修改作家

    System.out.println("W0");
    m_x = x; // non-volatile
    System.out.println("W1: " + x);
    m_y = y; // volatile
    System.out.println("W2: " + y);
    

    和读者线程代码

    System.out.println("R0");
    int y = m_y; // volatile
    System.out.println("R1: " + y);
    int x = m_x; // non-volatile
    System.out.println("R2: " + x);
    

    那为什么它不适合你?

    来自参考资料

      

    ...易失性,线程A在写入易失性字段f时可见的任何内容在读取f时都会被线程B看到。

    因此,只有在您的Writer线程将新值写入m_x时,您的Reader线程才能保证看到m_ym_y 的新值。但是因为没有保证特定的线程执行顺序,所以在Write-2执行之前可能不会发生写操作Read-1

    另请参阅Java Volatile Keyword by Jakob Jenkov了解与您类似的示例。

答案 1 :(得分:1)

你断言

  

“...之后(写入1),它不会将其缓存刷新到主存储器,因为m_x不是易失性的”

  

“... on(Read-2),它不会从主内存更新缓存,因为m_x不易变。”

实际上,无法判断缓存是否会被刷新(写入),或者缓存中是否存在值(读取)。 JLS肯定不会使&gt;&gt;任何&lt;&lt;保证读取和写入非易失性变量。保证仅适用于volatile变量的读写操作。

虽然您可能会在某些平台上观察到程序的一致行为,但JLS无法保证这种行为。