Java 在ConcurrentHashMap.ComputeiPresent中执行“remappingFunction”

Java 在ConcurrentHashMap.ComputeiPresent中执行“remappingFunction”,java,java-8,Java,Java 8,这是我最初的SO的后续问题 多亏了这个问题的答案,根据ConcurrentMap.ComputeiPresent看起来是这样的 当有多个线程时,默认实现可能会重试这些步骤 尝试更新,包括可能调用重新映射函数 多次 我的问题是: ConcurrentHashMap.ComputeiPresent调用remappingFunction时是否只在多个线程之间共享,或者在从单个线程创建和传递时是否也可以多次调用 如果是后一种情况,为什么会调用多次而不是一次 ConcurrentMap.ComputeiP

这是我最初的SO的后续问题

多亏了这个问题的答案,根据ConcurrentMap.ComputeiPresent看起来是这样的

当有多个线程时,默认实现可能会重试这些步骤 尝试更新,包括可能调用重新映射函数 多次

我的问题是:

ConcurrentHashMap.ComputeiPresent调用remappingFunction时是否只在多个线程之间共享,或者在从单个线程创建和传递时是否也可以多次调用

如果是后一种情况,为什么会调用多次而不是一次

ConcurrentMap.ComputeiPresent调用重新映射函数是否多次 它在多个线程之间共享或可以调用的次数 从单个线程创建和传递时多次

文档没有指定,但其含义是多个线程争用修改同一密钥的映射不一定全部通过ComputeiPresent,这可能会导致remappingFunction多次运行。我希望在将重映射结果设置为键的新值之前,实现会检查呈现给重映射函数的值是否仍然是与键关联的值。如果没有,它将重试,从新的当前值计算新的重新映射值

ConcurrentMap.ComputeiPresent调用重新映射函数是否多次 它在多个线程之间共享或可以调用的次数 从单个线程创建和传递时多次


文档没有指定,但其含义是多个线程争用修改同一密钥的映射不一定全部通过ComputeiPresent,这可能会导致remappingFunction多次运行。我希望在将重映射结果设置为键的新值之前,实现会检查呈现给重映射函数的值是否仍然是与键关联的值。如果没有,它将重试,从新的当前值计算新的重新映射值。

您可以在此处看到代码:

@Override
default V computeIfPresent(K key,
        BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    Objects.requireNonNull(remappingFunction);
    V oldValue;
    while((oldValue = get(key)) != null) {
        V newValue = remappingFunction.apply(key, oldValue);
        if (newValue != null) {
            if (replace(key, oldValue, newValue))
                return newValue;
        } else if (remove(key, oldValue))
           return null;
    }
    return oldValue;
}
如果线程1进入并调用remappingFunction并获取值X, 然后线程2在线程1等待时来更改值,只有线程1调用replace

由于值的变化,replace方法将返回false。 因此线程1将再次循环并再次调用remappingFunction


这可以继续下去,并创建无限次的重新映射函数调用。

您可以在这里看到代码:

@Override
default V computeIfPresent(K key,
        BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    Objects.requireNonNull(remappingFunction);
    V oldValue;
    while((oldValue = get(key)) != null) {
        V newValue = remappingFunction.apply(key, oldValue);
        if (newValue != null) {
            if (replace(key, oldValue, newValue))
                return newValue;
        } else if (remove(key, oldValue))
           return null;
    }
    return oldValue;
}
如果线程1进入并调用remappingFunction并获取值X, 然后线程2在线程1等待时来更改值,只有线程1调用replace

由于值的变化,replace方法将返回false。 因此线程1将再次循环并再次调用remappingFunction


这可以继续下去,并创建无限次的remappingFunction调用。

接口方法的一般约定允许实现在争用情况下重复求值,这正是ConcurrentMap继承默认方法时发生的情况,因为在默认方法中不可能在通用ConcurrentMap接口上提供原子性

但是,实现类ConcurrentHashMap会覆盖此方法,并在以下方面提供保证:

如果指定键的值存在,则尝试计算给定键及其当前映射值的新映射。整个方法调用是以原子方式执行的。在计算过程中,其他线程在此映射上尝试的某些更新操作可能会被阻止,因此计算应该简短,并且不得尝试更新此映射的任何其他映射

重点矿山


因此,由于您的问题特别要求ConcurrentHashMap.ComputeiPresent,答案是,它的参数函数永远不会被计算多次。这与ConcurrentSkipListMap.ComputeiPresent不同,例如,在ConcurrentSkipListMap.ComputeiPresent中,函数可能会被多次求值。

接口方法的一般约定允许实现在争用情况下重复求值,而这正是ConcurrentMap继承默认方法时发生的情况,因为在默认方法中不可能在通用ConcurrentMap接口上提供原子性

但是,实现类ConcurrentHashMap会覆盖此方法,并在以下方面提供保证:

如果指定键的值存在,则尝试计算给定键及其当前映射值的新映射。整个方法调用是以原子方式执行的。comp时,其他线程在此映射上尝试的某些更新操作可能会被阻止 置换正在进行中,因此计算应简短且简单,并且不得尝试更新此映射的任何其他映射

重点矿山



因此,由于您的问题特别要求ConcurrentHashMap.ComputeiPresent,答案是,它的参数函数永远不会被计算多次。这与ConcurrentSkipListMap.computeIfPresent不同,例如,该函数可能会被计算多次。

此代码来自何处?ConcurrentHashMap.ComputeiPresent源代码不同。@tsolakp,我没有检查,但我认为这是ConcurrentMap.ComputeiPresent的默认实现。不管你的标题如何,这是你提问的内容,而不是ConcurrentHashMap的实现。@John Bollinger。我的问题是关于ConcurrentHashMap.ComputeiPresen的,但是Javadoc注释来自ConcurrentMap.ComputeiPresent。ConcurrentHashMap.ComputeiPresent的Javadoc没有说明可以调用多少次remappingFunction。我的假设是,由于ConcurrentHashMap.ComputeiPresent javadoc没有说明其他内容,因此它从ConcurrentMap.ComputeiPresent继承了相同的javadoc契约。@tsolakp,ConcurrentHashMap在您的问题中出现的唯一位置就是标题。相反,您引用了ConcurrentMap接口的文档,而实际问题也询问了该接口:ConcurrentMap.ComputeiPresent调用remappingFunction多次[…]吗?重点补充。此外,你的问题只有在这种情况下才有意义,因为没有理由认为ConcurrentHashMap.ComputeiPresent会多次调用remappingFunction。@John Bollinger。你是对的,那是我的错误。无论如何,我会给这个答案一个分数,但接受你的答案,因为这个答案更接近我想要的问题。这个代码是从哪里来的?ConcurrentHashMap.ComputeiPresent源代码不同。@tsolakp,我没有检查,但我认为这是ConcurrentMap.ComputeiPresent的默认实现。不管你的标题如何,这是你提问的内容,而不是ConcurrentHashMap的实现。@John Bollinger。我的问题是关于ConcurrentHashMap.ComputeiPresen的,但是Javadoc注释来自ConcurrentMap.ComputeiPresent。ConcurrentHashMap.ComputeiPresent的Javadoc没有说明可以调用多少次remappingFunction。我的假设是,由于ConcurrentHashMap.ComputeiPresent javadoc没有说明其他内容,因此它从ConcurrentMap.ComputeiPresent继承了相同的javadoc契约。@tsolakp,ConcurrentHashMap在您的问题中出现的唯一位置就是标题。相反,您引用了ConcurrentMap接口的文档,而实际问题也询问了该接口:ConcurrentMap.ComputeiPresent调用remappingFunction多次[…]吗?重点补充。此外,你的问题只有在这种情况下才有意义,因为没有理由认为ConcurrentHashMap.ComputeiPresent会多次调用remappingFunction。@John Bollinger。你是对的,那是我的错误。无论如何,我会对这个答案给出一个分数,但接受你的答案,因为这个答案更接近我想要的问题。@tsolakp,我在ConcurrentHashMap.ComputeiPresent的文档中发现了这一点,但它没有记录在ConcurrentMap接口上,并且ConcurrentSkipListMap.ComputeiPresent明确否认原子性。@tsolakp,Java的并发数据结构通常避免锁定。这允许实现使用较低级别的原子操作,而实际上没有冲突的访问时,原子操作通常更快,并且允许更大范围的非冲突并发访问。@tsolakp,查看代码可能会有所启发,但您应该依赖的是文档。对于ConcurrentMap等接口的方法尤其如此,即使定义了默认实现。话虽如此,允许ConcurrentMap.ComputeiPresent实现多次运行remappingFunction背后的想法是让它们能够使用低级原子操作,而不是同步。任何特定的实现(如ConcurrentHashMap)都不使用该自由度是不相关的。@tsolakp您反复混淆ConcurrentMap接口和特定的ConcurrentHashMap实现。在ConcurrentMap.ComputeiPresent的默认实现中,没有同步。在ConcurrentHashMap.ComputeiPresent的具体实现中,您可以找到同步语句,但此方法提供了明确的原子性保证,因此不允许通过多次求值来避免锁定,因为这将与其规范相矛盾。@tsolakp但问题仍然引用了接口规范,您接受的答案与编辑之前一样,只解决了问题,i。

E接口强制执行的行为,而不是ConcurrentHashMap.ComputeiPresent实现的特定行为。因为,关于何时可能发生这种情况的问题是毫无意义的。@tsolakp,我在ConcurrentHashMap.ComputeiPresent的文档中发现了这一点,但它没有记录在ConcurrentMap接口上,并且ConcurrentSkipListMap.ComputeiPresent明确否认原子性。@tsolakp,Java的并发数据结构通常避免锁定。这允许实现使用较低级别的原子操作,而实际上没有冲突的访问时,原子操作通常更快,并且允许更大范围的非冲突并发访问。@tsolakp,查看代码可能会有所启发,但您应该依赖的是文档。对于ConcurrentMap等接口的方法尤其如此,即使定义了默认实现。话虽如此,允许ConcurrentMap.ComputeiPresent实现多次运行remappingFunction背后的想法是让它们能够使用低级原子操作,而不是同步。任何特定的实现(如ConcurrentHashMap)都不使用该自由度是不相关的。@tsolakp您反复混淆ConcurrentMap接口和特定的ConcurrentHashMap实现。在ConcurrentMap.ComputeiPresent的默认实现中,没有同步。在ConcurrentHashMap.ComputeiPresent的具体实现中,您可以找到同步语句,但此方法提供了明确的原子性保证,因此不允许通过多次求值来避免锁定,因为这将与其规范相矛盾。@tsolakp但问题仍然引用了接口规范,您接受的回答与编辑之前一样只解决了问题,即接口强制的行为,而不是ConcurrentHashMap.ComputeiPresent实现的具体行为。因为,关于何时会发生这种情况的问题是毫无意义的。你是在问规范还是实现?@shmosel。规格说明。那么为什么不一定是相关的或可回答的。@shmosel。抱歉,不知道你的意思?@tsolakp不相关,但提醒我:ConcurrentHashMap chm=new ConcurrentHashMap;chm.putone,1;chm.computeIfAbsenttwo,key->{chm.computeIfAbsenttwo,key->{return 2;};return 2;};System.out.printlnchm;用java-8和java-9运行这个程序是为了好玩……您是在询问规范还是实现?@shmosel。规格说明。那么为什么不一定是相关的或可回答的。@shmosel。抱歉,不知道你的意思?@tsolakp不相关,但提醒我:ConcurrentHashMap chm=new ConcurrentHashMap;chm.putone,1;chm.computeIfAbsenttwo,key->{chm.computeIfAbsenttwo,key->{return 2;};return 2;};System.out.printlnchm;使用java-8和java-9运行此程序是为了好玩…我将@John Bollinger answer标记为“不接受”。很抱歉,我的错误首先导致了此问题,并将此问题留给社区来决定哪一个是正确的。@tsolakp两个答案都是正确的,它们只是回答了不同的问题。我看到的问题是,未来的读者可能会感到困惑,因为很难识别实际问题是什么。请随意编辑问题。我的意图是按照规范重新映射ConcurrentHashMap.ComputeiPresent中的函数用法,而不是实际实现。这是对问题现在措辞方式的正确回答。根据我的理解,其他答案基于问题的原始措辞,现在没有任何意义。我将@John Bollinger答案标记为“不接受”。很抱歉,我的错误首先导致了这一点,并将由社区决定哪一个是正确的。@tsolakp两个答案都是正确的,他们只是回答不同的问题。我看到的问题是,未来的读者可能会感到困惑,因为很难识别实际问题是什么。请随意编辑问题。我的意图是按照规范重新映射ConcurrentHashMap.ComputeiPresent中的函数用法,而不是实际实现。这是对问题现在措辞方式的正确回答。根据我的理解,其他答案是基于问题的原始措辞,现在毫无意义。