Java 对象不需要易失性';成员,但仅在基本成员上?

Java 对象不需要易失性';成员,但仅在基本成员上?,java,multithreading,volatile,Java,Multithreading,Volatile,我的代码是 package threadrelated; import threadrelated.lockrelated.MyNonBlockingQueue; public class VolatileTester extends Thread { MyNonBlockingQueue mbq ; public static void main(String[] args) throws InterruptedException { VolatileTester vt =

我的代码是

package threadrelated;
import threadrelated.lockrelated.MyNonBlockingQueue;

public class VolatileTester extends Thread {

 MyNonBlockingQueue mbq ;

 public static void main(String[] args) throws InterruptedException {

    VolatileTester vt = new VolatileTester();
    vt.mbq = new MyNonBlockingQueue(10);
    System.out.println(Thread.currentThread().getName()+" "+vt.mbq);
    Thread t1 = new Thread(vt,"First");
    Thread t2 = new Thread(vt,"Secondz");
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(Thread.currentThread().getName()+" "+vt.mbq);

}
@Override
public void run() {
    System.out.println(Thread.currentThread().getName()+" before    "+mbq);
    mbq = new MyNonBlockingQueue(20);
    try {
        Thread.sleep(TimeUnit.SECONDS.toMillis(10));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+" after   "+mbq);
}

}
输出为

main threadrelated.lockrelated.MyNonBlockingQueue@72fcb1f4
Secondz before    threadrelated.lockrelated.MyNonBlockingQueue@72fcb1f4
First before    threadrelated.lockrelated.MyNonBlockingQueue@72fcb1f4
Secondz after   threadrelated.lockrelated.MyNonBlockingQueue@7100650c
First after   threadrelated.lockrelated.MyNonBlockingQueue@7100650c
main threadrelated.lockrelated.MyNonBlockingQueue@7100650c
它表明,当第一个线程将成员变量分配给新对象时,其他线程也可以看到该变量。即使“mbq”未声明为易失性

我使用断点尝试不同的操作序列。但我的观察是,一个线程可以立即看到另一个线程的影响


作为对象的类成员是否不需要volatile?它们总是同步到主内存吗?Volatile仅用于基本体成员变量(int、long、boolean等?)

引用和基本体一样需要Volatile。您的输出没有显示可见性问题这一事实并不证明可见性问题不存在。一般来说,很难证明并发错误不存在。但这里有一个简单的对策,说明了
volatile
的必要性:

public class Test {
    static volatile Object ref;

    public static void main(String[] args) {
        // spin until ref is updated
        new Thread(() -> {
            while (ref == null);
            System.out.println("done");
        }).start();

        // wait a second, then update ref
        new Thread(() -> {
            try { Thread.sleep(1000); } catch (Exception e) {}
            ref = new Object();
        }).start();
    }
}

此程序运行一秒钟,然后打印“完成”。删除
volatile
,它不会终止,因为第一个线程从未看到更新的
ref
值。(免责声明:与任何并发测试一样,结果可能会有所不同。)

通常,您在此时未看到发生的事情并不意味着以后不会发生。特别是对于并发代码。您可以使用
jcstress
库,并尝试向您展示代码可能存在的问题

Volatile变量与其他变量不同,因为它在CPU级别引入了
内存限制。没有这些,就无法保证何时或什么线程可以看到来自另一个线程的更新。简单地说,它们被称为
StoreLoad | StoreStore | LoadLoad | LoadStore


因此,使用volatile可以保证可见性效果,实际上它是可见性效果唯一可以依赖的东西(除了使用
不安全的
和锁定/同步关键字)。您还必须考虑到您正在为特定的CPU进行测试,很可能是
x86
。但是对于不同的CPU(比如说ARM)来说,事情会变得更快。

您的代码不是对volatile的有用测试。它将在有或没有挥发性物质的情况下工作,这不是偶然的,而是根据规范

包括对volatile关键字进行更好测试的代码,因为字段是否为volatile会产生影响。如果使用该代码,使字段不易失性,并在循环中插入println,那么应该可以看到从另一个线程设置的字段值是可见的。这是因为println在打印流上同步,插入了内存屏障

在您的示例中,还有两件事插入了这些障碍,导致更新在线程之间可见。 列表列出了这些关系之前发生的情况:

对线程的start()调用发生在已启动线程中的任何操作之前

线程中的所有操作都发生在任何其他线程从该线程上的join()成功返回之前

这意味着您发布的代码中不需要volatile。新启动的线程可以看到从main传入的队列,一旦线程完成,main可以看到对队列的引用。在线程启动和println执行之间有一个窗口,其中字段的内容可能过时,但代码中没有任何内容对其进行测试

但是不,说volatile不需要作为参考是不准确的。对于volatile,有一种先发生后发生的关系:

对易失性字段(§8.3.1.4)的写入发生在该字段的每次后续读取之前


规范没有区分包含引用的字段和包含原语的字段,规则适用于两者。这又回到了Java被值调用的问题,引用就是值。

您想要的代码是在您尝试时恰好可以工作的代码,还是标准保证可以工作的代码?“保证”:@shmosel抱歉。复制粘贴错误。添加了缺少的行。这个示例需要多个内核/处理器吗?@BasilBourque我只在自己的机器上尝试过,所以我不能确定。这对你不管用吗?我是说一般来说。我想知道您的示例代码演示的可见性问题在单核机器上是否不是问题?或者,由于CPU缓存的原因,线程在单个内核上的可见性可能会有所不同?这些更改发生在线程执行过程中,即在两个线程都启动之后。所以我不理解你强调的那一点。