1

Suppose that i have a shared ConcurrentHashMap<String, Integer> called map that has already only one mapping ("One", 1), and suppose also that i have 2 threads.

The first thread executes this code:

map.put("One", 2);

and the second thread executes this code:

synchronized (map) {
    Integer number = map.get("One");
    System.out.println(number == map.get("One"));
}

Since ConcurrentHashMap works with lock striping method instead of locking entire object i don't think that the described scenario is thread safe. Particularly i don't know if there could be an interleaving of map.put("One", 2); in first thread between Integer number = map.get("One"); call and System.out.println(number == map.get("One")); call in second thread despite both are inside a synchronized block.

So is it possible that that code prints false?

  • 4
    Your understanding is correct, the code may print false. Nothing prevents thread interleaving here. However, this does not mean that `put()` itself is not thread-safe (based on the title of your question). – M A Jan 22 '21 at 16:03
  • I think this question may help: https://stackoverflow.com/questions/14947723/is-concurrenthashmap-totally-safe – M A Jan 22 '21 at 16:05
  • 3
    @PanagiotisBougioukos The point of this question is that `get` is called twice in a row, and how to prevent that from resulting in two different results. Synchronization is necessary to prevent this, but the synchronization in the provided code does not do this as it is not synchronized using the same lock as the `put` method. – Pieter12345 Jan 22 '21 at 16:07
  • 2
    More generally, you could ask, "Can a thread-safe class be used in a non-thread-safe way?" And the answer is a resounding "Yes!" Building your program entirely out of thread-safe components does not automatically guarantee that your program will be thread-safe. – Solomon Slow Jan 22 '21 at 16:22
  • 4
    Following on from @SolomonSlow's comment: it's important to pay close attention to what "thread-safe" means: CHM is thread-safe from the perspective of its internal state; but code in which you use that CHM may have its own, separate invariants, which have separate thread safety requirements. In that case, it's your code which needs to ensure that your invariants are correctly preserved when accessed by multiple threads. – Andy Turner Jan 22 '21 at 16:24
  • 1
    Note that `ConcurrentHashMap` used lock striping in Java 7 and earlier. Since Java 8, it does not use lock striping. But even in those older versions, a plain `get` call would not lock anything in the ideal case. – Holger Jan 25 '21 at 10:00

1 Answers1

1

All methods within ConcurrentHashMap might be thread-safe, but this does not mean that it synchronizes on the ConcurrentHashMap object itself. What you can do is synchronize put and the map access code on the same reference. Your put code would have to be changed to this:

synchronized (map) {
    map.put("One", 2);
}

And your access code can remain like:

synchronized (map) {
    Integer number = map.get("One");
    System.out.println(number == map.get("One"));
}

This will never be able to print false.

Pieter12345
  • 1,713
  • 1
  • 11
  • 18
  • 1
    But in this case having a ConcurrentHashMap it's useless – Edoardo Ermini Jan 22 '21 at 15:57
  • 1
    That's true, a `ConcurrentHashMap` is convenient when you only want to synchronize single-method accesses to the `Map`. In this case, if these are all your accesses to that map, then you might as well be using a `HashMap`. Best practise would be to store the result from the `get` in a variable and use that variable twice. That way, the `ConcurrentHashMap` would suffice for synchronization. – Pieter12345 Jan 22 '21 at 16:04
  • 2
    @SolomonSlow Well, Edoardo said “*in this case*” and that’s correct. *In this case* you can use an ordinary `HashMap` and `synchronized`. – Holger Jan 25 '21 at 09:43