简体   繁体   English

在Integer上同步会导致NullPointerException

[英]Synchronizing on an Integer results in NullPointerException

This question is based on Synchronizing on an Integer value . 该问题基于对Integer值进行同步

The solution there seems excellent only there is small problem it does not address the concern how to delete values from ConcurrentHashMap . 那里的解决方案似乎很好,仅存在一个小问题,它没有解决如何从ConcurrentHashMap删除值的问题。

So to address that I did below program 所以要解决我在程序下面所做的

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

Problem is that it always results in NullPointerException . 问题在于它总是导致NullPointerException My first analysis was because I am deleting the value it gets assigned to null so it is causing NullPointerException . 我的第一个分析是因为我要删除将其赋值为null的值,所以会导致NullPointerException So I did below 所以我在下面做了

    Object obj = new Object();
    synchronized (obj) {
        obj = null;
    }

But above does not result in NullPointerException . 但以上不会导致NullPointerException So My question is why it is throwing NullPointerException in above case? 所以我的问题是,为什么在上述情况下会引发NullPointerException

Even if I do 即使我这样做

public Integer getLockForId(int id) {
   return concurrentHashMap.putIfAbsent(id, id); 
}

It still results in NullPointerException because it only returns value when there is one else return null 它仍然会导致NullPointerException因为它仅在存在另一个返回null时才返回值。

Well yes, that would throw a NullPointerException . 好吧,那抛出NullPointerException Consider this pattern: 考虑以下模式:

 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!)

It's not that the "value get assigned to null" - it's that Map.get returns null if there's no entry for the given key. 这不是“将值赋给null”, Map.get如果没有给定键的条目,则Map.get返回null。

It's hard to know what to recommend, as your code really doesn't do anything useful . 很难知道推荐什么,因为您的代码实际上并没有做任何有用的事情。 If you can say what you're trying to achieve in your real code, we can give you better suggestions, potentially. 如果您可以说出要在实际代码中实现的目标,那么我们可能会为您提供更好的建议。

EDIT: As noted by Nikita, just returning the value of putIfAbsent doesn't work, as that returns the previous value, or null if it was absent - whereas you want the new value for the entry. 编辑:如Nikita所述,仅返回putIfAbsent的值不起作用,因为它返回的是前一个值;如果不存在,则返回null ,而您想要该条目的值。

I suspect you'll have to synchronize access to the map, basically, to make your getLockId method atomic with respect to the remove operation. 我怀疑您基本上必须同步对地图的访问,以使getLockId方法相对于remove操作具有原子性。

You can try to make all accesses to concurrentHashMap synchronous. 您可以尝试使对concurrentHashMap HashMap的所有访问同步。 So when you get value from map you synchronize on concurrentHashMap and when you remove it. 因此,当您从地图中获取价值时,您将在concurrentHashMap HashMap上concurrentHashMap同步,并在将其删除时进行同步。 Something like this: 像这样:

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

Consider using google's LoadingCache instead. 考虑改用Google的LoadingCache。 It uses weak references, so garbage collection is left to the JVM; 它使用弱引用,因此垃圾回收留给了JVM。 you don't have to delete references you no longer need. 您不必删除不再需要的引用。 And it handles the concurrency issues involved with creating a new entry. 它处理创建新条目所涉及的并发问题。 The code for a value synchronizer would look like this: 值同步器的代码如下所示:

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

Actually syncing on a value would look like this: 实际同步一个值看起来像这样:

private final ValueSynchronizer<Long> retrySynchronizer 
    = new ValueSynchronizer<>();

@Override
public void onApplicationEvent(final EventThatCanBeSentMultipleTimes event)
{
  retrySynchronizer.sync(event.getEventId(), () ->
  {
    //...Your code here.
  });
}

(licensed under Apache 2.0 license: http://www.apache.org/licenses/LICENSE-2.0 ) (根据Apache 2.0许可获得许可: http : //www.apache.org/licenses/LICENSE-2.0

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM