对象不需要挥发性物质'成员但仅限于原始成员?

时间:2017-05-18 04:50:41

标签: 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 = 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等?)

3 个答案:

答案 0 :(得分:4)

它对于引用和原语一样必要。您的输出未显示可见性问题的事实并非证明一个不存在。通常,很难证明不存在并发错误。但这里有一个简单的反证明,表明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值。 (免责声明:与任何并发测试一样,结果可能会有所不同。)

答案 1 :(得分:0)

一般情况下,此时您没有看到发生的事情,并不意味着以后不会发生。对于并发代码,尤其为true。您可以使用memory barries库,并尝试向您展示代码可能出现的问题。

易失性变量与其他变量不同,因为它在CPU级别引入StoreLoad|StoreStore|LoadLoad|LoadStore。如果没有这些,什么线程看到来自另一个的更新时,无法保证。简单来说,这些被称为Unsafe

因此,使用volatile保证可见性效果,实际上它是唯一可以依赖的可见性效果(除了使用x86和locks / synchronized关键字)。您还必须考虑到您正在测试特定CPU ,最有可能是voterid | pres | vpres | sec | trea | PIO --------------------------------------------- 1 | John | Mitch | James | Jack | Eman 2 | John | Pao | Bryan | Jack | Faye 3 | Kelvin | Pao | James | Jeck | Faye 。但是对于不同的CPU(比如ARM让我们说),事情会更快地破坏。

答案 2 :(得分:0)

您的代码不是volatile的有用测试。它可以使用或不使用volatile,不是偶然的,而是根据规范。

Shmosel's answer包含对volatile关键字进行更好测试的代码,因为该字段是否是易失性的结果。如果您使用该代码,使字段非易失性,并在循环中插入println,那么您应该看到来自另一个线程的字段值集是可见的。这是因为println在打印流上同步,插入了内存屏障。

您的示例中还有两个插入这些障碍的内容,导致跨线程可以看到更新。 Java Language Specification列出了这些发生在以前的关系:

  

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

     

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

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

但是不,说引用不需要volatile是不准确的。对于volatile来说,有一个先发生过的关系:

  

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

规范不区分包含引用的字段和包含基元的字段,该规则适用于两者。这回到Java是按值调用,引用是值。