我的代码是
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等?)
答案 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是按值调用,引用是值。