Java.Core. If we read null in multithread enviroment, doesn’t it mean we have no value there ?

Great follow-up! Let’s walk through this slowly — you’re thinking logically, but there’s a subtle trap when multiple threads are involved. Let me break it down step by step, and I promise this will click by the end.


🔥 First, what does null mean in a Map?

In any Map, null can mean two different things:

CaseMeaning
1️⃣ Key is missingThe key does not exist in the map (so get() returns null).
2️⃣ Key exists, value is nullThe key exists, but its associated value is null.

🔥 In a single-threaded environment, this is manageable:

You have control, so you know how your code uses null values.

Example:

map.put("key1", null);  // Key exists, value is null
map.remove("key2");     // Key does not exist

System.out.println(map.get("key1")); // null — value is null
System.out.println(map.get("key2")); // null — key is missing

It’s your choice whether null means “nothing here” or “this key is deliberately set to null.” You can control the meaning.


🔥 Now, enter multi-threading

  • Thread 1 writes:
map.put("key1", null);

Thread 2 reads at exactly the same moment:

String value = map.get("key1");

Now, when value is null, Thread 2 has no reliable way to know why. Two possibilities exist:

CaseExplanation
1️⃣ Key was removed a millisecond agonull means “key does not exist anymore.”
2️⃣ Key exists, but someone set value to nullnull means “key exists, value is deliberately null.”

🔥 The core problem: The meaning of null changes depending on timing.

  • Between the time Thread 2 called get() and processed the result, Thread 1 could have removed the key or set its value to null.
  • This leads to race conditions — the meaning of null is no longer clear or reliable when multiple threads are modifying the map concurrently.

💡 Example – Race Condition in Action

map.put("user123", "ACTIVE");

// Thread 1: Deletes user
map.remove("user123");

// Thread 2: Checks if user is active
String status = map.get("user123");  // returns null
if (status == null) {
    System.out.println("User does not exist");
}

// Now what happens if Thread 1 does this instead?
map.put("user123", null);  // User exists, but is marked as "no state"

// In this case, `get()` will also return null — now `Thread 2` has no idea:
   // Was the user removed?
   // Or does the user exist, but with null state?

🔥 Why does ConcurrentHashMap just ban null?

This ambiguity does not exist if null is simply forbidden. It forces you to write unambiguous logic.

✅ Instead of:

map.put("user123", null);  // ambiguous

You do this:

map.remove("user123");     // user123 is gone, clear meaning

Or, if you want to keep the key but show “no value”:

map.put("user123", "NO_STATE");   // explicit placeholder

This way:

  • null always means “key not present.”
  • A value (even “NO_STATE”) means “key exists, and this is the current value.”

🔔 This is the whole reason:

If null is allowedIf null is forbidden
null could mean missing key OR null valuenull always means key is missing
You have to guess why null appearedNo guessing — always clear
Race conditions when two threads competeNo ambiguity in race conditions

💡 In short: null is ambiguous in concurrent code. ConcurrentHashMap forces you to write clear, race-free logic.


🔨 Short example of correct code using ConcurrentHashMap:





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

// Thread 1
map.put("user123", "ACTIVE");

// Thread 2 - safely remove key if needed
map.remove("user123");

// Thread 3 - safely check
String value = map.get("user123");
if (value == null) {
    System.out.println("User does not exist");
} else {
    System.out.println("User state: " + value);
}

✅ Final takeaway:

In ConcurrentHashMap, null means key is not present — always.
This removes a whole class of subtle race conditions.

This entry was posted in Без рубрики. Bookmark the permalink.