Synchronizing on an Integer results in NullPointerException

让人想犯罪 __ 提交于 2019-12-11 11:08:24

问题


This question is based on Synchronizing on an Integer value.

The solution there seems excellent only there is small problem it does not address the concern how to delete values from 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. My first analysis was because I am deleting the value it gets assigned to null so it is causing NullPointerException. So I did below

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

But above does not result in NullPointerException. So My question is why it is throwing NullPointerException in above case?

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


回答1:


Well yes, that would throw a 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.

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.

I suspect you'll have to synchronize access to the map, basically, to make your getLockId method atomic with respect to the remove operation.




回答2:


You can try to make all accesses to concurrentHashMap synchronous. So when you get value from map you synchronize on concurrentHashMap and when you remove it. 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);
    }
}



回答3:


Consider using google's LoadingCache instead. It uses weak references, so garbage collection is left to the 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)



来源:https://stackoverflow.com/questions/12890214/synchronizing-on-an-integer-results-in-nullpointerexception

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!