Java Concurrency Increase Value

I read about volatile and synchronized in Java, but scratched my head in confusion. I hope someone helps me solve the problem.

 private HashMap<String,int> map = new HashMap<String,int>(); 

In my topic

 if (map.get("value") == null) { map.put("value",0); } map.put("value",map.get("value")+1); 

My goal is for all threads to share this map . If I add volatile , this does not seem to fix the problem for me (I output and see that the map redefined every time). Then I tried using ConcurrentHashMap and adding volatile before that ... which also doesn't work. Based on my reading about volatile , I understand that it should “block” access to map when map written, and then when map is executed, it is written in a lock.

So ... then I tried adding static

 private static ConcurrentHashMap<String,int> map = new ConcurrentHashMap<String,int>(); 

and this seems to work fine ... But ... I keep reading that using static not the right way because of something about "competition" (which I don't quite understand)

Thank you in advance

+4
source share
3 answers

Volatile won't help here. Volatile is useful for solving visibility problems, but you have another problem: atomicity .

Oh, and Volatile has nothing to do with locking. It will not acquire a lock when reading / writing, it will not release anything. He does this: all the actions that happened before are written in the volatile field, will be visible to every other stream after they read the same volatile field. There is no lock (they are similar to the fact that the memory effects when releasing / acquiring a lock are exactly the same).

The get and set operations are not atomic, which means that other things can happen between them.

For example, one thread will be a get value, then the ANOTHER stream will get same value, both increase the value, then the first will be set new value, then the second will do the same, the final result is not what you expected.

The most common solution to this problem is to serialize access (i.e. synchronize ) to a shared variable or use comparison and set (CAS) (so you don't need to perform synchronization).

1. synchronized

 private final Map<String, Integer> m = new ConcurrentHashMap<String, Integer>(); synchronized incrementValue(final String valueName) { m.put(valueName, m.get(valueName) + 1); } 

Please note that if you use this solution, then EVERY ACCESS to the card must be synchronized with the same lock.

2. CAS

Many CAS algorithms are already implemented in the JVM in a very efficient way (i.e., they use their own code, and the JIT can use instructions specific to the processor, which you cannot access in other ways - check the Unsafe class in the Sun JVM, for example).

One class that might come in handy is AtomicInteger . You can use it as follows:

 private final Map<String, AtomicInteger> m = new ConcurrentHashMap<String, AtomicInteger>(); incrementValue(final String valueName) { m.get(valueName).incrementAndGet(); } 

What the CAS algorithm will do is something like this:

 for (;;) { state = object.getCurrentState(); if (object.updateValueAndStateIfStateDidntChange(state)) { break; } } 

The updateValueAndStateIfStateDidntChange method is updateValueAndStateIfStateDidntChange be atomic and will return true only if it was able to update the value. Thus, if another thread changes the value after you get the state, and before you update the value, the method will return false, and the loop will try again.

Assuming that you can implement this method in a way that does not use synchronized (and you can, using classes in java.util.concurrent), avoid conflicts (this means that threads waiting to receive a lock are held by other threads), and you can see the overall performance improvement.

I use a lot of this in a distributed task system that I wrote. All tasks must be performed exactly once, and I have many machines that perform tasks. All tasks are set in one MySQL table. How to do it? You must have a column whose purpose is to implement CAS. Name it executing . Before starting the task, you should do something like: get the following task, "update tasks set executing = 1 where id = :id AND executing = 0" and count the number of updated lines . If you updated 0 lines, this is due to the fact that another thread / process / machine has already completed this task (and successfully executed this "update" request); in this case, you will forget it and try the next task, because you know that this one is already running. If you updated 1 line, then it’s good to go, you can execute it.

Another place where I use this idea of ​​CAS a lot is in a very dynamic (by its configuration) resource pool that I wrote (I use it mainly to manage "connections", ie sockets, but it is quite general for storage any kind of resources). Basically, it counts how many resources it holds. When you try to get a resource, it reads the counter, decreases it, tries to update it (if nothing has changed the counter between them), and if it succeeds, you can simply take the resource from the pool and borrow it (as soon as the counter reaches 0, it will not provide a resource). If I ever publish this code, I will definitely add a link to it here.

+10
source

The answer, of course, should be used by AtomicInteger , not int .

And if you use ConcurrentHashMap , you need to use your special thread-safe putIfAbsent() method.

Here is an excerpt of javadoc from this method:

It is equivalent

 if (!map.containsKey(key)) return map.put(key, value); else return map.get(key); 

except that the action is atomic.

 private ConcurrentMap<String, AtomicInteger> map = new ConcurrentHashMap<String, AtomicInteger>(); AtomicInteger value = map.get("value"); if (value == null) { map.putIfAbsent("value", new AtomicInteger()); value = map.get("value"); } value.incrementAndGet(); 

Note that you do not need to return a new value.

If you make this change in your code, everything should work fine.

0
source

Volatile ensures that threads do not cache this value. Volatile ensures that writing / reading this value from memory is atomic, but does not lock this variable in multi-stage mode.

Replace the map with ConcurrentHashMap and use the atomic CAS operation.

Usage: ConcurrentHashMap.putIfAbsent ()

0
source

Source: https://habr.com/ru/post/1386451/


All Articles