Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can we achieve read on mutable data after write atomically in ConcurrentHashMap in Java?

I'm trying to find an answer to these, but not able to understand(or confirm) it on Google or in Java docs.

My implementation looks like as this:

Map<String, POJO> map = new ConcurrentHashMap<String, POJO>();

if I do

value1 = map.get(key1);
value1.setProp(prop);

Any other thread may override.

Now, I'm thinking if I do like follows: would it be an atomic operation/in another words, it will block the key1 segment?

map.compute(key1, (key1, value1) -> { value1.setProp(prop) });

Javadoc for compute function

Attempts to compute a mapping for the specified key and its current mapped value (or null if there is no current mapping). The entire method invocation is performed atomically. Some attempted update operations on this map by other threads may be blocked while computation is in progress, so the computation should be short and simple, and must not attempt to update any other mappings of this Map.

References:

1.https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html 2.https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html#compute-K-java.util.function.BiFunction-

EDIT:

for my final implementation, I did something like this as all threads share NewMap and at the end, I new list of POJOs
Abstract Data Type

    public class NewMap {

        private Map<String, POJO> map;
        private boolean isUpdatable;
        void NewMap(){
              isUpdatable = true;
              map = new ConcurrentHashMap();
        }

        void putPOJOProp1(String key, Type value) throws ReadOnlyException{

                  map.compute(key, (k,v) -> {
                           if(!isUpdatable) throw new ReadOnlyException();
                           if(k == null){ 
                              POJO p = new POJO();
                              p.setProp1(value);
                              v = p;
                           } else { 
                              v = v.setProp1(v)
                           }
             });
        }

        void putPOJOProp2....
        void putPOJOProp3....

        List<POJO> getAll() {
             isUpdatable = false;
             List<POJO> pojos;
             for(key: map.getKeys()) {
                  Pojo p = map.get(key);
                  p.setKey(key);
                  pojos.add(p);
             }
             return pojos;
        }
    }
like image 604
Mitesh Joshi Avatar asked Jul 28 '18 07:07

Mitesh Joshi


People also ask

Does ConcurrentHashMap in Java uses the read write lock?

ConcurrentHashMap class is thread-safe i.e. multiple threads can operate on a single object without any complications. At a time any number of threads are applicable for a read operation without locking the ConcurrentHashMap object which is not there in HashMap.

Can 2 threads perform write operations on same ConcurrentHashMap object?

Having two threads that change the map at the very same point time is not possible. Because the code within that ConcurrentHashMap will not allow two threads to manipulate things in parallel!

How do you atomically update a value in ConcurrentHashMap?

The compute(Key, BiFunction) method of ConcurrentHashMap class is used to compute a mapping for the specified key and its current mapped value (or null if there is no current mapping is found). This method is used to atomically update a value for given key in ConcurrentHashMap.

Does ConcurrentHashMap allow null values?

The main reason that nulls aren't allowed in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) because there will be ambiguities that may be just barely tolerable in non-concurrent maps can't be accommodated. The main one is that if map.


2 Answers

Your confusing two different terms.
ConcurrentHashMap prevents corruption of internal structure, but doesn't prevent race conditions. If all you want to avoid is data corruption, then use of ConcurrentHashMap is valid.
But from your question it seems that you're trying to avoid race conditions. ConcurrentHashMap doesn't protect you from them in any way.

To understand it better, let's see the following example:

    Map<String, POJO> map = new ConcurrentHashMap<>();
    ExecutorService pool = Executors.newWorkStealingPool(10);

    for (int t = 0; t < 10; t++) {
        pool.execute(() -> {
            for (int i = 0; i < 100_000; i++) {
                map.compute("A", (k, v) -> {
                    if (v == null) {
                        return new POJO();
                    }
                    v.a = ++v.a;
                    v.b = ++v.b;

                    return v;
                });
            }
        });
    }

    pool.awaitTermination(5, TimeUnit.SECONDS);

    System.out.println(map);

    // With POJO like 
    class POJO {
       // toString() here
       Integer a = 1;
       Integer b = 1;
    }

Here we get {A=POJO{a=1000000, b=1000000}}, so our operation was thread safe. If that's all you want you get, you're fine.

like image 136
Alexey Soshin Avatar answered Oct 23 '22 03:10

Alexey Soshin


The Javadoc for ConcurrentHashMap.compute states

The entire method invocation is performed atomically.

Note, by comparison the ConcurrentSkipListMap.compute is NOT atomic.

A more compact form of Alexey Soshin's answer is

Map<String, long[]> map = new ConcurrentSkipListMap<>();
map.put("A", new long[2]);
IntStream.range(0, 1_000_000)
        .parallel()
        .forEach(i -> map.compute("A", (k, v) -> {
            v[0]++;
            v[1]++;
            return v;
        }));
System.out.println(Arrays.toString(map.get("A")));

prints something like

[643553, 597254]

c.f. HashMap produces something like

[244786, 245993]

However, use a ConcurrentHashMap and you get the expected

[1000000, 1000000]
like image 24
Peter Lawrey Avatar answered Oct 23 '22 03:10

Peter Lawrey