如果您只有一个写线程,您是否需要特殊的并发?

men*_*ics 10 java concurrency

假设:

  1. 只有一个特定的线程可以设置某个引用字段(不是long或double,因此写入它是原子的)
  2. 有任意数量的线程可能会读取同一个字段
  3. 稍微陈旧的读取是可以接受的(最多几秒钟)

在这种情况下,您需要volatile或AtomicReference或类似的东西吗?

本文指出:

如果严格遵守单一编写原则,则不需要内存屏障.

这似乎表明,在我描述的情况下,你真的不需要做任何特别的事情.

所以,这是一个测试我跑了好奇的结果:

import org.junit.Test;

public class ThreadTest {
    int onlyWrittenByMain = 0;
    int onlyWrittenByThread = 0;

    @Test
    public void testThread() throws InterruptedException {
        Thread newThread = new Thread(new Runnable() {
            @Override
            public void run() {
                do {
                    onlyWrittenByThread++;
                } while (onlyWrittenByMain < 10 || onlyWrittenByThread < 10);
                System.out.println("thread done");
            }
        });
        newThread.start();

        do {
            onlyWrittenByMain++;
            // Thread.yield();
            // System.out.println("test");
            // new Random().nextInt();
        } while (onlyWrittenByThread < 10);
        System.out.println("main done");
    }
}
Run Code Online (Sandbox Code Playgroud)

有时运行它会输出"thread done"然后永远挂起.有时它确实完成了.所以线程看到主线程所做的更改,但显然main并不总是看到线程所做的更改?

如果我把系统放入,或者Thread.yield,或者随机调用,或者只使用DynamicWyThread,它每次都会完成(尝试大约10次以上).

这是否意味着我上面提到的博客文章不正确?即使在单个编写器场景中,您也必须具有内存屏障?

没有人完全回答这个问题,所以我想我认为不需要内存屏障是正确的,但如果没有创建事先关系的东西,java编译器和热点可以进行优化(例如,吊装)将使它不做你想做的事.

sel*_*lig 5

问题是在多核系统上进行缓存 - 没有像volatile这样的事情会强制发生之前的关系(内存屏障的东西)你可以让你的编写器线程写入其核心缓存中的变量副本以及所有读取器线程读取另一个另一个核心上的变量副本.另一个问题是原子性,另一个答案解决了这个问题.


Sal*_*kci 1

这是必需的,因为读取字段上的更改可能对读取器线程不可见。您应该创建一个发生在关系之前。