此问题基于Synchronizing on an Integer value。
那里的解决方案似乎很好,只有小问题,它没有解决如何从ConcurrentHashMap
删除值的问题。
所以要解决我在节目下面做的事情
import java.util.concurrent.ConcurrentHashMap;
public class Example {
private final ConcurrentHashMap<Integer, Integer> concurrentHashMap = new ConcurrentHashMap<Integer, Integer>();
public void doSomething(int i) {
synchronized (getLockForId(i)) {
concurrentHashMap.remove(i);
}
}
public Integer getLockForId(int id) {
concurrentHashMap.putIfAbsent(id, id); // I want to replace these two
// operation with single one
// since it seems the cause of
// NPE
return concurrentHashMap.get(id);
}
public static void main(String[] args) {
final Example example = new Example();
new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (true) {
example.doSomething(++i);
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (true) {
example.doSomething(++i);
}
}
}).start();
}
}
问题是它始终会产生NullPointerException
。我的第一个分析是因为我正在删除它被赋值为null的值,因此它导致NullPointerException
。所以我在下面做了
Object obj = new Object();
synchronized (obj) {
obj = null;
}
但上面不会导致NullPointerException
。所以我的问题是为什么它会在上面的案例中抛出NullPointerException
?
即使我这样做
public Integer getLockForId(int id) {
return concurrentHashMap.putIfAbsent(id, id);
}
它仍会产生NullPointerException
,因为只有当其他人返回null
答案 0 :(得分:8)
是的,将抛出NullPointerException
。考虑这种模式:
Thread 1 Thread 2
putIfAbsent (puts)
get (returns non-null)
acquire monitor
putIfAbsent (doesn't put)
remove (removes value)
get (returns null)
acquire monitor (bang!)
并不是“值被赋值为null” - 如果给定键没有条目,则Map.get
返回null。
很难知道推荐什么,因为您的代码实际上没有做任何有用的。如果您可以在真实代码中说明您要实现的目标,我们可以为您提供更好的建议。
编辑:正如Nikita所述,只返回putIfAbsent
的值不起作用,因为它返回之前的值,如果不存在则返回null
- 而你想要条目的 new 值。
我怀疑您必须同步对地图的访问权限,基本上是为了使getLockId
方法相对于remove
操作具有原子性。
答案 1 :(得分:2)
您可以尝试对concurrentHashMap
的所有访问进行同步。因此,当您从地图获取价值时,您会在concurrentHashMap
上同步并在删除时同步。像这样:
public void doSomething(int i) {
synchronized (getLockForId(i)) {
// do stuff
synchronized (concurrentHashMap) {
concurrentHashMap.remove(i);
}
}
}
public Integer getLockForId(int id) {
synchronized (concurrentHashMap) {
concurrentHashMap.putIfAbsent(id, id);
return concurrentHashMap.get(id);
}
}
答案 2 :(得分:0)
请考虑使用谷歌的LoadingCache。它使用弱引用,因此垃圾收集留给JVM;您不必删除不再需要的引用。它处理创建新条目所涉及的并发问题。值同步器的代码如下所示:
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ValueSynchronizer<T> {
private final LoadingCache<T, Lock> valueLocks;
public ValueSynchronizer() {
valueLocks = CacheBuilder.newBuilder().build(
new CacheLoader<T, Lock>() {
public Lock load(final T id) {
return new ReentrantLock();
}
});
}
public void sync(final T onValue, final Runnable toDo)
{
final Lock lock = valueLocks.getUnchecked(onValue);
lock.lock();
try {
toDo.run();
}
finally {
lock.unlock();
}
}
}
实际上,对值进行同步将如下所示:
private final ValueSynchronizer<Long> retrySynchronizer
= new ValueSynchronizer<>();
@Override
public void onApplicationEvent(final EventThatCanBeSentMultipleTimes event)
{
retrySynchronizer.sync(event.getEventId(), () ->
{
//...Your code here.
});
}
(根据Apache 2.0许可证授权:http://www.apache.org/licenses/LICENSE-2.0)