java:带有getter和setter的`volatile`私有字段

kha*_*hik 14 java volatile memory-model java-memory-model

我们应该声明私有字段,就像volatile在多个线程中使用instanced一样吗?

Effective Java中,有一个例子,代码在没有volatile的情况下不起作用:

import java.util.concurrent.TimeUnit;

// Broken! - How long would you expect this program to run?
public class StopThread {
    private static boolean stopRequested; // works, if volatile is here

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested)
                    i++;
            }
        });
        backgroundThread.start();
        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}
Run Code Online (Sandbox Code Playgroud)

解释说

while(!stopRequested)
    i++;
Run Code Online (Sandbox Code Playgroud)

优化到这样的事情:

if(!stopRequested)
    while(true)
        i++;
Run Code Online (Sandbox Code Playgroud)

所以stopRequested后台线程看不到进一步的修改,所以它永远循环.(顺便说一句,该代码在没有volatileJRE7的情况下终止.)

现在考虑这个类:

public class Bean {
    private boolean field = true;

    public boolean getField() {
        return field;
    }

    public void setField(boolean value) {
        field = value;
    }
}
Run Code Online (Sandbox Code Playgroud)

和一个如下的线程:

public class Worker implements Runnable {
    private Bean b;

    public Worker(Bean b) {
        this.b = b;
    }

    @Override
    public void run() {
        while(b.getField()) {
            System.err.println("Waiting...");
            try { Thread.sleep(1000); }
            catch(InterruptedException ie) { return; }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码按预期工作,不使用volatile:

public class VolatileTest {
    public static void main(String [] args) throws Exception {
        Bean b = new Bean();

        Thread t = new Thread(new Worker(b));
        t.start();
        Thread.sleep(3000);

        b.setField(false); // stops the child thread
        System.err.println("Waiting the child thread to quit");
        t.join();
        // if the code gets, here the child thread is stopped
        // and it really gets, with JRE7, 6 with -server, -client
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为,由于公共setter,编译器/ JVM永远不应该优化调用的代码getField(),但本文说有一些"Volatile Bean"模式(模式#4),应该应用于创建可变的线程安全类.更新:也许该文章仅适用于IBM JVM?

问题是:JLS的哪一部分明确地或隐含地说具有公共getter/setter的私有原始字段必须声明为volatile(或者它们不必)?

很抱歉,我试图详细解释这个问题.如果有什么不清楚,请告诉我.谢谢.

Joo*_*kka 9

问题是:JLS的哪一部分明确地或隐含地说具有公共getter/setter的私有原始字段必须声明为volatile(或者它们不必)?

JLS内存模型不关心getter/setter.从内存模型的角度来看,它们是无操作的 - 你也可以访问公共领域.将布尔值包装在方法调用后面不会影响其内存可见性.你的后一个例子纯粹是运气.

如果实例在多个线程中使用,我们是否应该将私有字段声明为volatile?

如果要在多线程环境中使用类(bean),则必须以某种方式将其考虑在内.制作私有字段volatile是一种方法:它确保每个线程都能保证看到该字段的最新值,而不是任何缓存/优化过时的过时值.但它并没有解决原子性问题.

您链接的文章适用于任何符合JVM规范(JLS倾向于此)的JVM.您将获得各种结果,具体取决于JVM供应商,版本,标志,计算机和操作系统,运行程序的次数(HotSpot优化经常在第10000次运行后启动)等,因此您必须了解规范并仔细遵守遵守规则以创建可靠的计划.在这种情况下进行试验是一种很难找到工作方式的方法,因为JVM可以以任何方式运行,只要它符合规范,并且大多数JVM确实包含所有类型的动态优化.


Joh*_*int 4

在回答你的问题之前我想先解决一下

顺便说一句,该代码在 JRE7 上终止且没有 易失性

This can change if you were to deploy the same application with different runtime arguments. Hoisting isn't necessarily a default implementation for JVMs so it can work in one and not in another.

To answer your question there is nothing preventing the Java compiler from executing your latter example like so

@Override
public void run() {
    if(b.getField()){
        while(true) {
            System.err.println("Waiting...");
            try { Thread.sleep(1000); }
            catch(InterruptedException ie) { return; }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

It is still sequentially consistent and thus maintains Java's guarantees - you can read specifically 17.4.3:

Among all the inter-thread actions performed by each thread t, the program order of t is a total order that reflects the order in which these actions would be performed according to the intra-thread semantics of t.

A set of actions is sequentially consistent if all actions occur in a total order (the execution order) that is consistent with program order, and furthermore, each read r of a variable v sees the value written by the write w to v such that:

In other words - So long as a thread will see the read and write of a field in the same order regardless of the compiler/memory re ordering it is considered sequentially consistent.