繁体   English   中英

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

[英]java: `volatile` private fields with getters and setters

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

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;
    }
}

解释说

while(!stopRequested)
    i++;

优化到这样的事情:

if(!stopRequested)
    while(true)
        i++;

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

现在考虑这个类:

public class Bean {
    private boolean field = true;

    public boolean getField() {
        return field;
    }

    public void setField(boolean value) {
        field = value;
    }
}

和一个如下的线程:

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; }
        }
    }
}

上面的代码按预期工作,不使用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
    }
}

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

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

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

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

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

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

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

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

不,该代码同样不正确。 JLS中没有任何内容表示必须将字段声明为volatile。 但是,如果您希望代码在多线程环境中正常工作,则必须遵守可见性规则。 volatile和synchronized是两个正确使数据在线程间可见的主要工具。

至于你的例子,编写多线程代码的难点在于许多形式的错误代码在测试中都能正常工作。 仅仅因为多线程测试在测试中“成功”并不意味着它是正确的代码。

有关特定的JLS参考,请参阅“ 发生前”部分(以及页面的其余部分)。

请注意,作为一般的经验法则,如果您认为自己想出了一种巧妙的新方法来绕过“标准”线程安全习语,那么您很可能是错的。

在我回答你的问题之前,我想解决

顺便说一下,该代码在JRE7上没有volatile就终止了

如果您使用不同的运行时参数部署相同的应用程序,则可能会更改。 吊装不一定是JVM的默认实现,因此它可以在一个而不是另一个中工作。

要回答你的问题,没有什么能阻止Java编译器执行你的后一个例子

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

它仍然是顺序一致的 ,因此保持了Java的保证 - 你可以具体阅读17.4.3

在由每个线程t执行的所有线程间动作中,t的程序顺序是反映根据t的线程内语义将执行这些动作的顺序的总顺序。

如果所有动作都以与程序顺序一致的总顺序(执行顺序)发生,则一组动作是顺序一致的,此外,变量v的每个读取r将写入w写入的值看作v,使得:

换句话说 - 只要线程将以相同的顺序看到字段的读取和写入,而不管编译器/内存的重新排序,它被认为是顺序一致的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM