2

我在使用 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;
    }

输出

key 2008 thread 1
save
session save
key 2008 thread 0
update Year{name =2008, pk =null}
year pk null thread 0
save
session save
t ouput Year{name =2008, pk =1}

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

4

1 回答 1

2

来自ConcurrentHashMapAPI:

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

在您的方法参数中声明您的ConcurrentHashMap最终结果,并在同步块中对其进行编辑。

public foo(final ConcurrentHashMap concurrentMap) {
    synchronized (concurrentMap) {
        //Your code here
    }
}

这将强制每个线程Object在修改它之前检索 concurrentMap 上的锁,这将解决您的竞争条件。

另外,如果你需要多个线程同时访问地图,而在上面的foo()方法中做应用程序代码时只需要加锁,那么为获取方法创建一个锁,而不是使用地图本身。

final Object fooLock = new Object();

public foo(final ConcurrentHashMap concurrentMap) {
    synchronized (fooLock) {
        //Your code here
    }
}

关于第二个例子的更多解释:

假设我有一个 ConcurrentHashMap,其键为 String,值为 Year。不同的线程可以访问它以添加/删除值,并且我想在一定范围内对 Years 运行分析,同时不阻止我的程序在我的分析运行时添加/删除值。

如果锁定 ConcurrentHashMap,其他线程将无法添加/删除值,直到锁定被删除。

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

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

假设这一切都在一个Analyzer类中声明。我也可以像这样声明 runAnalysis() 方法:

public synchronized void runAnalysis(ConcurrentHashMap map) {
    //Do analysis logic
}

这个方法不是在我们的“锁”对象上获取锁,而是在Analyzer实例上获取锁。一种稍微不同的方法,通常比创建自己的锁更常见。

请注意,如果我这样做,则在 runAnalysis() 运行时,任何其他声明为“同步”的方法都将被阻止,反之亦然。您可以使用锁变得任意复杂,但如果您只需要一种方法来同步类实例,那么获取实例锁而不是单独为方法创建锁看起来更简洁。

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

于 2013-10-30T05:11:05.787 回答