I have tons of update operation on ConcurrentHashMap and I need to minimize synchronization for update this map.
please see code below.
public ConcurrentHashMap<String, Integer> dataMap = new ConcurrentHashMap<>();
public void updateData(String key, int data) {
if ( dataMap.containsKey(key)) {
// do something with previous value and new value and update it
}
}
For example, When one thread calls updateData with Key "A", then every other try calling updateData with key "A" should be blocked until first thread is done. meanwhile, I want another thread trying to call updateData with key "B" runs concurrently.
I wonder if there is any fancy way to lock hashMap simply with its key.
I think you are looking for the compute method.
It takes a function that is given the key and the current value (or null if there is no value yet), and can compute the new value.
ConcurrentHashMap guarantees that only one such functions runs at the same time for the given key. A concurrent second call will block until ready.
Related
I stumbled upon the following piece of code:
public static final Map<String, Set<String>> fooCacheMap = new ConcurrentHashMap<>();
this cache is accessed from rest controller method:
public void fooMethod(String fooId) {
Set<String> fooSet = cacheMap.computeIfAbsent(fooId, k -> new ConcurrentSet<>());
//operations with fooSet
}
Is ConcurrentSet really necessary? when I know for sure that the set is accessed only in this method?
As you use it in the controller then multiple threads can call your method simultaneously (ex. multiple parallel requests can call your method)
As this method does not look like synchronized in any way then ConcurrentSet is probably necessary here.
Is ConcurrentSet really necessary?
Possibly, possibly not. We don't know how this code is being used.
However, assuming that it is being used in a multithreaded way (specifically: that two threads can invoke fooMethod concurrently), yes.
The atomicity in ConcurrentHashMap is only guaranteed for each invocation of computeIfAbsent. Once this completes, the lock is released, and other threads are able to invoke the method. As such, access to the return value is not atomic, and so you can get thread inference when accessing that value.
In terms of the question "do I need `ConcurrentSet"? No: you can do it so that accesses to the set are atomic:
cacheMap.compute(fooId, (k, fooSet) -> {
if (fooSet == null) fooSet = new HashSet<>();
// Operations with fooSet
return v;
});
Using a concurrent map will not guarantee thread safety. Additions to the Map need to be performed in a synchronized block to ensure that two threads don't attempt to add the same key to the map. Therefore, the concurrent map is not really needed, especially because the Map itself is static and final. Furthermore, if the code modifies the Set inside the Map, which appears likely, that needs to be synchronized as well.
The correct approach is to the Map is to check for the key. If it does not exist, enter a synchronized block and check the key again. This guarantees that the key does not exist without entering a synchronized block every time.
Set modifications should typically occur in a synchronized block as well.
I have a ConcurrentMap which I need to populate from multithread application. My map is shown below:
private final ConcurrentMap<String, AtomicLongMap<String>> deviceErrorHolder = Maps.newConcurrentMap();
Below is my method which is called from multithreaded application at very fast rate so I need to make sure it is fast.
public void addDeviceErrorStats(String deviceName, String errorName) {
AtomicLongMap<String> errorMap = deviceErrorHolder.get(deviceName);
if (errorMap == null) {
errorMap = AtomicLongMap.create();
AtomicLongMap<String> currenttErrorMap = deviceErrorHolder.putIfAbsent(deviceName, errorMap);
if (currenttErrorMap != null) {
errorMap = currenttErrorMap;
}
}
errorMap.incrementAndGet(errorName);
}
For each deviceName, I will have an AtomicLongMap which will contain all the counts for different errorName.
ExceptionCounter.getInstance().addDeviceErrorStats("deviceA", "errorA");
ExceptionCounter.getInstance().addDeviceErrorStats("deviceA", "errorB");
ExceptionCounter.getInstance().addDeviceErrorStats("deviceA", "errorC");
ExceptionCounter.getInstance().addDeviceErrorStats("deviceB", "errorA");
ExceptionCounter.getInstance().addDeviceErrorStats("deviceB", "errorB");
Is my addDeviceErrorStats method thread safe? And also the way I am updating the value of my deviceErrorHolder map is correct? Meaning will it be an atomic operation? Do I need to synchronize creation of new AtomicLongMap instances? Or CM will take care that for me?
I am working with Java7.
You can create a lot simpler version of this with computeIfAbsent().
AtomicLongMap<String> errorMap = deviceErrorHolder.computeIfAbsent(deviceName, a -> AtomicLongMap.create());
errorMap.incrementAndGet(errorName);
The computeIfAbsent (in concurrent maps) is especially meant to do an atomic version of what your null checking logic does. If the deviceName key has a value, it's returned, otherwise the computation is called atomically, and the return value of the computation is both associated with the key in the map as well as returned.
I believe your method is correct. Let's assume we have two concurrent threads calling it for the same device
The case where the errorMap already existed is trivial, as both threads will get the same and call incrementAndGet on it, which is atomic.
Let's now consider the case where errorMap didn't exist. say the first thread gets to AtomicLongMap.create(), and then the second thread is scheduled. Such thread will also create its own local map. putIfAbsent() is atomic, hence one of the threads will return null, while the second will return the map put by the first. In the latter case, you're throwing away the map that was instantiated by this thread, and using the one returned instead. Looks good to me.
I want to use the concurrent hash map holding some results,
ConcurrentHashMap<Long,AtomicInteger>
add a new entry if key not exists,or get value by key and increment,like this:
if(map.contains(key))
map.get(key).addAndGet(1);
else
map.put(key,new AtomicInteger(1));
the put operation is not thead safe,how to solve this problem? Is put operation should within synchronized block?
The put() operation itself is implemented in a threadsafe way, i.e. if you put the same key it will be synchronized internally.
The call, however, isn't, i.e. two threads could add a new key simultaneously. You could try putIfAbsent() and if you get a return value (i.e. not null) you could call the get method. Thus you could change your code like this:
//this only adds a new key-value pair if there's not already one for the key
if( map.putIfAbsent(key,new AtomicInteger(1)) != null ) {
map.get(key).addAndGet(1);
}
Alternatively if you're using Java 8 you could use the compute() method which according to the JavaDoc is performed atomically. The function you pass would then check whether the value already exists or not. Since the whole call is synchronized you probably wouldn't even need to use a AtomicInteger (depends on what else you are doing with the value).
In Java 8 you could use ConcurrentHashMap's computeIfAbsent to provide initial value:
map.computeIfAbsent(key, new AtomicInteger(0)).addAndGet(1)
You should use the ConcurrentHashMap.putIfAbsent(K key, V value) and pay attention to the return value.
public void tSafe(List<Foo> list, Properties status) {
if(list == null) return;
String key = "COUNT";
AtomicInteger a = new AtomicInteger(Integer.valueOf(status.getProperty(key,"0")));
list.parallelStream().filter(Foo::check).
forEach(foo -> {status.setProperty(key, String.valueOf(a.incrementAndGet())); }
);
}
private interface Foo {
public boolean check();
}
Description:
In the above example, status is a shared properties and it contains a key with name COUNT. My aim is to increment count and put it back in properties to count the number of checks performed. Consider tSafe method is being called by multiple threads, Do I get the correct count at the end? Note that I've used AtomicInteger a as local variable.
If you only have one thread, this will work, however if you have more than one thread calling this, you have some operations which are thread safe. This will be fine provided each thread operates on different list and status objects.
As status is a thread safe collection, you can lock it, and provided the list is not changed in another thread, this would would.
In general, working with String as numbers in a thread safe manner is very tricky to get right. You are far better off making your value thread i.e. an AtomicInteger and never anything else.
No this will not guarantee thread safety. Even though incrementAndGet() is itself atomic, getting a value from the Properties object and setting it back is not.
Consider the following scenario:
Thread #1 gets a value from the Properties object. For argument's sake let's say it's "100".
Thread #2 gets a value from the Properties object. Since nothing has happened, this value is still "100".
Thread #1 creates an AtomicInteger, increments it, and places "101" in the Properties object.
Thread #2 does exactly the same, and places "101" in the Properties object, instead of the 102 you expected.
EDIT:
On a more productive note, a better approach would be to just store the AtomicInteger on your status map, and increment it inplace. That way, you have a single instance and don't have to worry about races as described above. As the Properties class extends Hashtable<Object, Object> this should technically work, although Properties really isn't intended for values that aren't Strings, and you'd be much better off with a modern thread safe Map implementation, such as a ConcurrentHashMap:
public void tSafe(List<Foo> list, ConcurrentMap<String, AtomicInteger> status) {
if(list == null) {
return;
}
String key = "COUNT";
status.putIfAbsent(key, new AtomicInteger(0));
list.parallelStream()
.filter(Foo::check)
.forEach(foo -> { status.get(ket).incrementAndGet(); });
}
Have a quick synchronization question, here is what I have:
a) Class1 has a concurrent hash map defined as follows:
ConcurrentMap<String, int[][]> map = new ConcurrentHashMap<String, int[][]>();
b) Class2 has a thread, called Thread1. Thread1 creates an Id and checks if map contains it. If it does, it retrieves the value (int[][]), modifies the contents and puts it back. If it doesn't, it creates a new int[][] and stores it. This process of check->modify/create happens frequently.
private class Thread1 implements Runnable{
public void run(){
//keepRunning is volatile
while( keepRunning ){
String id = "ItemA";
int[][] value = map.get(id);
//If value is null, create an int[][] and put it back as value for Id
//If value is not null, modify the contents according to some logic
}
}
}
c) Finally, I have another thread, called Thread2. This thread takes an id, checks if the map has a value for it. If it doesn't, nothing happens. If it does, then it sums up the values in int[][] and uses the number for some calculation (no modifications here).
I am trying to figure out if my operations are atomic.
Operations in b) are fine as the creation/modification of the array and insertion into the map is confined to only one thread (Thread1).
Also, since insertion into the map establishes a happens-before action, this will ensure that c) will see the updated values in int[][].
However, I am not too sure of what would happen if Thread2 looks up the same int[][] in the map and tries to sum it up while Thread1 is modifying it.
Am I correct in thinking that Thread2 will see the older (but uncorrupted) values in int[][]. Reason being that until Thread1 has finished putting the value back into the map, the new modifications wont be visible to Thread2.
Thanks very much.
Your operations are not atomic, thread 2 will be trying to sum the values while thread 1 is modifying them.
To avoid that you'll need to duplicate the original modify the duplicate and put back the copy.