Java 具有多线程和putIfAbsent的争用条件

Java 具有多线程和putIfAbsent的争用条件,java,multithreading,thread-safety,Java,Multithreading,Thread Safety,我在使用putIfAbsent时遇到了一个问题,第二个线程将在第一个线程使用pk完成值更新之前尝试访问该值 示例代码 public <T> Object getLookupValue(final Class<T> type, String key, ConcurrentHashMap<String, T> concurrentMap) { try { T value = concurrentMap.get(key);

我在使用putIfAbsent时遇到了一个问题,第二个线程将在第一个线程使用pk完成值更新之前尝试访问该值

示例代码

public <T> Object getLookupValue(final Class<T> type, String key, ConcurrentHashMap<String, T> concurrentMap) {
        try {

            T value = concurrentMap.get(key);

            if (value == null) {
                System.out.println("save");
                T t = type.getDeclaredConstructor(String.class).newInstance(key);
                Object returnedValue = concurrentMap.putIfAbsent(key, t);

                if (returnedValue == null) {
                    System.out.println("session save");
                    session.save(t);
                    System.out.println("t ouput " + t.toString());
                    return t;
                }
                return concurrentMap.get(key);
            } else {    
                System.out.println("update" + concurrentMap.get(name));
                return concurrentMap.get(key);
            }
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException ex) {
            System.out.println("getLookupValue " + ex);
            Logger.getLogger(LineReaderParserImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

有人知道为什么在线程0完成添加pk之前调用线程1,或者为什么线程0在生成pk之前添加对象吗?

来自
ConcurrentHashMap
API:

但是,即使所有操作都是线程安全的,检索操作也不需要锁定,并且不支持以阻止所有访问的方式锁定整个表

在方法参数中声明
ConcurrentHashMap
final,并在同步块中对其执行编辑

public foo(final ConcurrentHashMap concurrentMap) {
    synchronized (concurrentMap) {
        //Your code here
    }
}
这将迫使每个线程在修改concurrentMap
对象之前检索一个锁,这将解决您的竞争条件

此外,如果需要多个线程同时访问映射,但在执行上述
foo()
方法中的应用程序代码时只需要锁定,请为要获取的方法创建一个锁,而不是使用映射本身

final Object fooLock = new Object();

public foo(final ConcurrentHashMap concurrentMap) {
    synchronized (fooLock) {
        //Your code here
    }
}
ConcurrentHashMap<String, Year> concurrentMap;

final Object lock = new Object();

public void runAnalysis(final ConcurrentHashMap map) {
    /*synchronized (map) {
        //This will cause addValue() to lock up while the analysis is running
    }*/

    synchronized (lock) {
        //Now we can run a long-running analysis and not block the addValue() method

        //Additionally, if another thread calls runAnalysis(), it must wait to 
        //get our lock (when a current running analysis is completed) 
        //before it can start
    }

}

//This method needs access to concurrentMap, so we can't lock it
public void addValue() {
    concurrentMap.add("key", new Year());
}
关于第二个示例的更多解释:

假设我有一个ConcurrentHashMap,它的键是字符串,值是年份。不同的线程可以访问它来添加/删除值,我希望在一定范围内对年份运行分析,同时不阻止程序在分析运行时添加/删除值

如果对ConcurrentHashMap进行了锁定,则在移除该锁定之前,其他线程将无法添加/删除值

在第二个示例中,我为要抓取的方法创建了一个不同的锁,因此它不会锁定映射本身

final Object fooLock = new Object();

public foo(final ConcurrentHashMap concurrentMap) {
    synchronized (fooLock) {
        //Your code here
    }
}
ConcurrentHashMap<String, Year> concurrentMap;

final Object lock = new Object();

public void runAnalysis(final ConcurrentHashMap map) {
    /*synchronized (map) {
        //This will cause addValue() to lock up while the analysis is running
    }*/

    synchronized (lock) {
        //Now we can run a long-running analysis and not block the addValue() method

        //Additionally, if another thread calls runAnalysis(), it must wait to 
        //get our lock (when a current running analysis is completed) 
        //before it can start
    }

}

//This method needs access to concurrentMap, so we can't lock it
public void addValue() {
    concurrentMap.add("key", new Year());
}
此方法将获取
分析器
实例上的锁,而不是获取“lock”对象上的锁。这是一种稍微不同的方法,通常比创建自己的锁更常见

请注意,如果执行此操作,则声明为“synchronized”的任何其他方法将在runAnalysis()运行时被阻止,反之亦然。使用锁可以变得非常复杂,但是如果只需要一个方法来同步类实例,那么获取实例锁比单独为方法创建锁看起来更干净


您应该查阅一些关于Java中多线程、同步和锁的教程。

谢谢,是的,我需要多个线程才能同时访问地图,您能更好地解释一下第二个示例吗?线程外部的映射如下所示,final ConcurrentHashMap yearMap=getLookupMap(Year.class);我添加了更多的细节,也许能解释为什么第二个会更好。我想我几乎能理解这一点,我想我唯一能理解的部分是“我仍然不完全理解何时调用addValue vs runAnalysis?我不应该只在密钥不存在时才添加到concurrentMap吗?感谢您迄今为止的帮助。这两种方法都只是虚构的例子。这证明了在对象或类实例上使用锁时,仍然可以修改/读取ConcurrentMap。如果锁定ConcurrentMap,您将无法从其他线程修改/读取它。我假设在调用
会话期间分配了
pk
。save(t)
?@MikeStrobel正确,hibernate分配了pk。