首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >实现原子操作是因为java的易失性保证发生在关系之前吗?

实现原子操作是因为java的易失性保证发生在关系之前吗?
EN

Stack Overflow用户
提问于 2015-12-23 03:18:47
回答 2查看 115关注 0票数 2

实现原子操作是因为java的易失性保证发生在关系之前吗?

我以前读过关于易失性发生的事情:

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

现在,我有两个变量:

代码语言:javascript
复制
         static int m_x;
volatile static int m_y;

现在我有两个线程,一个只写,先写m_x,然后写m_y;另一个只读,先读m_y,再读m_x。

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

在我的理解中,它们应该是原子的:

(1)在写线程方面,在(Write-1)之后,由于m_x不是易失性的,所以它不会将其缓存刷新到主存中,因此,读线程不能看到更新;并且在(Write-2)之后,由于m_y是易失性的,它将其缓存刷新到主存中;

(2)在读线程方面,由于m_y是易失性的,它将从主内存中更新其缓存;而在( read -2)中,它将不会从主内存中更新其缓存,因为m_x不是易失性的。

由于以上两个原因,我认为读线程应该始终观察这两个变量的原子值。对吗?

代码语言:javascript
复制
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));
}

}

EN

回答 2

Stack Overflow用户

发布于 2015-12-23 06:01:18

正如注释中所述,这两个读取和写入不是原子的。不能通过使用volatile关键字来实现原子性。

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

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

做后一件事

代码语言:javascript
复制
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 concurrency in practice一书。

附录

...

由于以上两个原因,我认为读线程应该始终观察这两个变量的原子值。对吗?

错误!

...and你遗漏了一个重要的部分。

有关参考,请参阅JSR 133 (Java Memory Model) FAQ by Jeremy Manson and Brian Goetz小节易失性是做什么的?

在您的程序中,没有任何东西会阻止以下操作:

假设int m_x = x1,int m_y = y1

  1. 您的写入器-线程将一直执行,直到现在将Write-1 x2设置为值(可能对您的写入器可见,也可能不可见

  1. 你的写入器-线程的执行被挂起(无论出于什么原因)
  2. 你的读取器-线程执行Read-1Read-2 (没有什么能阻止读取器线程这样做)
    • int y = m_y,它仍然是y1,因为你的写入器线程没有进一步执行yet
    • int x = m_x,它可能是x2 (但它仍然可以是x2

  1. 你的阅读器-线程被挂起,而你的写入器线程继续,
    • int m_y现在被设置为值y2 (只有现在Read-1将获得y2,Read-2将被保证获得x2 -除非你的写入器线程continues)

  1. ...

来看看你自己修改了你的作者

代码语言:javascript
复制
System.out.println("W0");
m_x = x; // non-volatile
System.out.println("W1: " + x);
m_y = y; // volatile
System.out.println("W2: " + y);

和读取器线程代码

代码语言:javascript
复制
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);

那么为什么它对你不起作用呢?

参考资料

...volatile与否,任何在线程A写入易失性字段f时对线程A可见的内容,在线程B读取f时都会变为可见。

因此,只有当你的写入器线程你的写入器线程将新值写入m_y时,你的读取器线程才能看到m_xm_y 的新值。但是因为不能保证特定的线程执行顺序,所以在执行Read-1之前可能不会发生写操作Write-2

另请参阅Java Volatile Keyword by Jakob Jenkov,以获取与您的示例类似的示例。

票数 1
EN

Stack Overflow用户

发布于 2015-12-24 19:25:19

你断言

"...after (Write-1),它不会将其缓存刷新到主内存,因为m_x不是易失性的“

"... on (Read-2),它不会从主内存更新其缓存,因为m_x不是易失性的。“

事实上,不可能说缓存是否会被刷新(写),或者缓存中是否存在值(读)。JLS当然不会为对非易失性变量的读写提供>>any<<保证。这些保证仅适用于对易失性变量的读写操作。

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

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34422966

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档