46

After another question asked on stackoverflow, (Java- Why this program not throwing concurrent Modification exception) I started to experiment with the HashMap. Here are a few lines of code which I wrote:

import java.util.HashMap;
import java.util.Random;

public class Concurrency {
    public static void putEntriesToMap(HashMap<String, String> hashMap) {
        for (int count = 0; count < 10000; count++) {
            hashMap.put(Integer.toString(count), Integer.toString(count));
            Random random = new Random();
            if (random.nextBoolean()) {
                hashMap.remove(count + "");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final HashMap<String, String> hashMap = new HashMap<String, String>();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                putEntriesToMap(hashMap);
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                putEntriesToMap(hashMap);
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }
}

Once upon a time (approx 1 in 20 runs), when executing this code, I get

Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.ClassCastException: java.util.HashMap$Node cannot be cast to java.util.HashMap$TreeNode
    at java.util.HashMap$TreeNode.moveRootToFront(HashMap.java:1819)
    at java.util.HashMap$TreeNode.treeify(HashMap.java:1936)
    at java.util.HashMap.treeifyBin(HashMap.java:771)
    at java.util.HashMap.putVal(HashMap.java:643)
    at java.util.HashMap.put(HashMap.java:611)
    at Concurrency.putEntriesToMap(Concurrency.java:9)
    at Concurrency$1.run(Concurrency.java:27)
    at java.lang.Thread.run(Thread.java:745)

This, however, seems strange to me, because it looks like it is an internal HashMap error. I know that the concurrency is not used correctly, but it is done on purpose.

I tried to google the exception, but I found almost no info.

Can you even reproduce the same exception?

I am using oracle jdk 1.8.0_40

EDIT:

Firstly, thanks for answers, it is now clear for me. I just want to point out that I knew how to avoid the program from breaking by using thread safe precautions, but I didn't know why specifically this exception is thrown in given situation. Thomas has explained it very well in the comments below. It is also well explained in the accepted answer. Thanks again :).

Community
  • 1
  • 1
pnadczuk
  • 907
  • 1
  • 7
  • 12
  • 3
    It's simply consequence of "concurrency is not used correctly" -- `HashMap` needs external sync to be thread-safe. – Victor Sorokin Apr 30 '15 at 12:11
  • Thats possible, when you dont synchronize between different threads, whats the question here ? – Akash Yadav Apr 30 '15 at 12:12
  • 1
    Use collections from concurrent packages instead: http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ConcurrentMap.html – Beri Apr 30 '15 at 12:14
  • The question is, why is the exception so strange. After looking at HashMap code, it seems like **Node** object is casted on **TreeNode**, and it is not a **TreeNode** instance. That is why Exception is thrown. But why does it only happen when concurrency is used in a wrong way? – pnadczuk Apr 30 '15 at 12:15
  • 15
    As you can see Java 8's HashMap has a method called `treeify` to improve internal storage. Since you're not using the map in a threadsafe way (as the others already commented) one thread relies on an entry being of class `TreeNode` while the other most probably changed the same reference to an entry which was of class `Node` (both extend `Map.Entry`). – Thomas Apr 30 '15 at 12:15
  • Thanks Thomas, you explained it very reasonably. Now I think I understand. – pnadczuk Apr 30 '15 at 12:17

3 Answers3

32

I also found the same exception with your code. I added a synchronized modifier on the putEntriesToMap() method, and the error seemed to stop occurring. The problem is that both threads are modifying the same map at once. There is an object that must be converted to put the entry in. However, the second thread is dealing with a mutated object, which throws a ClassCastException. So, make sure that no two threads are accessing the same map at once. The synchronized modifier stops all other threads from doing anything with the class/instance if another thread is doing the same. Synchronized static methods synchronize the class itself, whereas synchronized non-static methods only synchronize the instance of the class.

hyper-neutrino
  • 5,272
  • 2
  • 29
  • 50
9

I was getting the same ClassCastException with concurrent calls to HashMap.computeIfAbsent. I fixed by changing the implementation to use ConcurrentHashMap.

Unmitigated
  • 76,500
  • 11
  • 62
  • 80
Clay
  • 10,885
  • 5
  • 47
  • 44
0

I resolved this just making clean and build to the project, and it works

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 23 '21 at 03:11