0

在新的 Java 内存模型中,对变量的任何写入都保证在下一个线程读取它之前完成。

我想知道作为该对象成员的变量是否也是这种情况。

对于java内存模型:

http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html

例如

public class VolatileTest {
    private volatile Map<String, Function> functionMap = new HashMap<>();

    public Function getFunction(String key) {
        Function function = this.functionMap.get(key);

        if (function == null) {
            //Is this guaranteed to be fully constructed? I don't think so.
            function = new Function(key);

            this.functionMap.put(key, function);
        }

        return function;
    }
}

和上面的代码一样,即使设置了functionMapvolatile,也不能保证函数对象在这个方法返回之前就已经完全构造好了。

我的想法对吗?

也只是为了这个话题,我想让你们检查一下我的想法是否适合以下内容:

像下面的任何写入functionMap保证在更改引用之前完成functionMap,对吧?无论initializeMap方法需要多长时间,其他线程要么看到 null 要么看到functionMap完全初始化的functionMap

public Map<String,Function> getFunctionMap (){
    Map<String, Function> tempMap = new HashMap<>();

    initalizeMap(tempMap); //fill in values

    // Above operation is guaranteed to be completed
    //   before changing the reference of this variable.
    this.functionMap = tempMap;

    // So here you will either see a null or a fully initialized map.
    // Is my understanding right?
    return this.functionMap;
}

上面澄清一下,上面两个例子都是在多线程环境下,functionMap变量会被多线程访问。

4

3 回答 3

1

这正是应该使用 ConcurrentHashMap 的时候

private final ConcurrentMap<String, Function> functionMap = new ConcurrentHashMap<>();

public Function getFunction(String key) {
        Function function = functionMap.get(key);
        if (function == null) {
            function = new Function(key);
            Function oldFunction = functionMap.putIfAbscent(function);
            if (oldFunction != null) {
                 function = oldFunction;
            }
        }
        return function;
}
于 2013-06-14T10:01:58.467 回答
1

@grumpynerd嗨,您的第一个假设是正确的,新函数(键)可以在方法返回后部分构造,除非它是不可变对象。

你的第二个假设是错误的。

 this.functionMap = tempMap;

    // So here you will either see a null or a fully initialized map.
    // Is my understanding right?
    return this.functionMap;

现在在这里,您正在编写和读取下一个相同的易失性变量。并且您假设 initializeMap 将在返回语句之前完成。您的假设漏洞在罗奇汽车旅馆模型中为真。但它在 JMM 中不一定正确。因为 JMM 是比 Roach 更弱的模型。请参阅以下问题的答案这是没有 volatile 和同步开销的更好版本的 Double Check Locking

从这里更新,因为它不能在评论中出现。很抱歉,您的第二个假设也成立。但请记住 this.functionMap = tempMap; 可能在实践中表现得好像所有语句都在它之前执行并刷新到内存中,正如 Doug lea 食谱 http://g.oswego.edu/dl/jmm/cookbook.html所解释的那样,但这只是关于如何实施 JMM 的指南。例如,查看这里的代码 这 是没有 volatile 和同步开销的更好版本的 Double Check Locking

在这里我尝试使用易失性栅栏作为内存屏障,即使你做出声明

fence = true; 
fence = fence;

现在我在这里进行易失性读写,假设这将使我的对象在返回引用之前完全初始化,因此如果我再次检查该引用是否为非空,那么我确信该对象将被完全初始化。但是上述推理是有缺陷的,因为我试图根据内存屏障而不是 java 发生之前的保证来思考。我的问题的缺陷是字段引用不是易失性的,因此任何其他线程都没有读取任何易失性引用。如果它找到的字段 !- null 即使这样,它也可能为 FieldType 的其他实例字段读取错误的值。因为在读取非易失性值时,java 无法保证。

因此,从不考虑限制语句的顺序。始终认为 JLS/JMM 所保证的事情发生在关系之前。

例如

整数计数 = 1;易失的布尔标志=真;

现在 jls 声明上述代码将按程序顺序运行。即 count 将首先为 1,然后 flag = true 将被执行。

    But Not the catch above from jls http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html

        It should be noted that the presence of a happens-before relationship 
between two actions does not necessarily imply that they 
have to take place in that order in an implementation. 
If the reordering produces results consistent with a legal execution, 
it is not illegal.

        For example, the write of a default value to every 
field of an object constructed by a thread need not 
happen before the beginning of that thread, as long as no 
read ever observes that fact.

上面说count=1可以在flag=true之后执行。如果执行是合法执行。但是我们的观察就像 count=1 已经先执行了一样。关键词是观察。(如果假设编译器发现该标志未在其他线程之间共享,则它也可能在这里发生,那么它可能是合法的..这只是假设)

但是现在如果在另一个线程中执行下面的语句那么

    if(flag == true) {
       sysout(count) // count is  guaranteed  to be 1 
       // because of volatile guarantees flag will be only true if it has been set by first thread
       // and by program order count must have been set before flag has been set to  true.
       // therefore by program order i am printing count after reading value of a 
//volatile variable flag, therefore count must be 1.
    }

现在,如果 flag 不是 volatile ,那么两个线程之间就没有发生之前的关系。线程 2 可以很好地读取 flag == true 并计为 0,因为线程之间没有观察保证。

简而言之,保证是为了观察,而不是每条语句的实际执行方式。阅读更多关于程序订单、同步订单和订单前发生的信息,让我们分享知识:) http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.3

于 2013-07-03T05:17:30.607 回答
0

volatile仅当变量值发生变化并且被多个线程访问时,标记变量才有用。你functionMap是对地图的引用,除非你想为这个引用分配一个新的 Map 实例,否则这里的 volatile 是没用的。需要明确的是, volatile 不会影响 Map 内容,只会影响 Map指针

我觉得您认为 Java 方法调用是异步的,事实并非如此。在您的第一个示例new Function(key);调用将不会返回,直到Function对象完全初始化。

第二个也是一样,除非initalizeMap启动一个新线程来做这件事,否则这里没有问题。即使在intializeMap使某些异步工作的情况下,如您所拥有的那样new HashMap<>(),tempMap 也不能为空,可能未完全加载由 initalizeMap 添加的值(我仅在initalizeMap启动新线程时重复)但不为空。

于 2013-06-14T08:49:08.170 回答