java - Double Check Lock 在此 java 代码上不起作用?

标签 java multithreading thread-safety singleton locking

我正在尝试通过优化同步块(synchronized block)来修复服务。我得到两个不同的值,并且我的带有 volatile 字符串的双重检查单例似乎不起作用?

get 和 Increment 字符串在数据库上放置行锁并递增字符串,以便仅在数据库级别进行唯一更新。所以在第一种情况下,没有问题。

问题出在 else block 上。当相关 ID 不为空时,如果这是第一次调用,我们将尝试获取已映射的值。然后我们首先映射该值,然后返回它。此映射必须同步,以便两个不同的线程在发现下一个 val 为 null 时不会更新它。

这个类也是一个单例服务。

public class RangeQueryService{

private volatile String nextValue=null;


public String getNextIncrement(String name, String correlationId) throws SomeCheckedException {
    try {
            if (correlationId == null) {

                nextValue = rangeFetch.getAndIncrementAsString(name);

            } else { //Enter Sync branch

                // mapper Will Return null if no value is mapped.

                nextValue = mapper.mapToB(SOME_CONST, correlationId);

                // Avoid syncronization overhead if value is already fetched. Only enter if nextVal is null.

                if (nextValue == null) {

                    synchronized (this) {
                        Doubly Check lock pattern, as two threads can find null simultaneously, and wait on the critical section.

                        if(nextValue==null){
                            nextValue = rangeFetch.getAndIncrementAsString(name);
                            idMapper.mapToB(SOME_CONST, correlationId, nextValue, DURATION);
                        }
                    }
                }
            }

        return nextValue;
    } catch (Exception e) {
        throw new SomeCheckedException("Error!" + e.getMessage());
    }
}

它同时返回 19 和 20。它应该只返回 19。

输出:

headerAfterProcessOne: 0000000019, headerAfterProcessTwo: 0000000020

最佳答案

如果我以正确的方式理解您,您会期望一个线程(我们称之为 A)等待另一个线程(将值增加到 19,B),然后跳过增量,因为 nextValue 是 19 并且不为 null。但等待线程看不到更改。

据我所知,可能的情况要复杂得多:

问题在于 A 线程返回 19,即等待的线程,因为在 B 线程在以下行发布 volatile 值后,它立即跳过整个 block :

nextValue = rangeFetch.getAndIncrementAsString(name);

另一种情况,A线程进入该方法并且nextValue已经发布。

所以它立即跳转到return语句并返回19,这是由B线程设置的(是的,这是意外的,但有时会发生这种情况)。您不应期望 A 线程等待 B 完成执行。 B(首先到达同步块(synchronized block))完成处理(递增)值并返回 20。

不过,有可能的解决方法:

if (nextValue == null) {
    synchronized(this) {
        if(nextValue == null) {
            String localTemp = rangeFetch.getAndIncrementAsString(name);
            idMapper.mapToB(SOME_CONST, correlationId, localTemp, DURATION);
            nextValue = localTemp;
        }
    }
}

总体来说,对 nextValue 所做的更改立即会影响 getNextIncrement 的其他调用。

在这种模式下调试问题真的很困难,所以我可能是错的,但无论如何我都会发布一个答案,因为我的解释对于评论来说太长了。

关于java - Double Check Lock 在此 java 代码上不起作用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59983281/

相关文章:

java - 如何在 Java 中很好地杀死一个线程?

java - Intellij regex String用方法参数替换类声明的新实例

java - 如果日志格式有多个空格,则 logger.info 的输出带有问号 (?)

android - CountDownTimer 更新和阻塞

c# - 线程安全日志类实现

c# - 请帮助我使这段代码线程安全

java - 如何在 HQL (hibernate sql) 中使用一个 like 运算符在多列中进行搜索

java - 隐式锁定 - 实际锁定的内容

c++ - boost线程notify_all()和sleep()

java - 使用 volatile 和atomic 总是能保证线程安全吗