3

We have a class named Subscriber, that extends "HashMap". We have many instances of this class in a list, and each instance has a set of entries set to the map, one of these are "status".

Our program is updating the "status" value by calling a method on the Subscriber, that does a simple put to the HashMap.

Our program can be running for multiple days (weeks) without any issues, but some times we have seen strange behaviour in some other parts of the system, that uses data from the HashMap. For us, it looks like there is a duplicate key in one or more of the Subscriber instances.

We manage to create a jmap dump, and based on the dump it looks for me that we have the "status" set twice when I look at the dump in VisualVM.

Screenshot of VisualVM

We are currently running Java version: 1.7.0_25 (Oracle)

How is this possible? Or do I read the VisualVM wrong?

goddva
  • 231
  • 1
  • 4
  • 2
    Are you writing to the map from multiple threads? – Jon Skeet Nov 24 '14 at 11:57
  • What class is used as a key of you map? – AlexR Nov 24 '14 at 11:59
  • @user3074713: With any synchronization? If not, that may well be the problem - `HashMap` doesn't support updates from multiple threads without external synchronization. – Jon Skeet Nov 24 '14 at 12:01
  • @AlexR We are using Strings – goddva Nov 24 '14 at 12:01
  • 1
    All I can imagine is that either you're modifying entry values using reflection somehow, or you're accessing the map from multiple threads without proper synchronization, leaving the map in an inconsistent state. – JB Nizet Nov 24 '14 at 12:01
  • 4
    HashMap is not thread-safe. Google that for more details, you need to either make it accessed from one thread, or use ConcurrentHashMap instead. Anyway it is a very questionable practice to inherit from HashMap. – Ilya Smagin Nov 24 '14 at 12:04
  • @JonSkeet Are you saying that running put from multiple threads can give duplicate keys? I have tried to create a simple test program that is doing just this, and it never failes. – goddva Nov 24 '14 at 12:05
  • 2
    @user3074713: Threading issues are notoriously hard to reproduce. But fundamentally, if you're using a class in multiple threads when it says not to, you at least can't rule out that being the cause of the problem. Consider either adding locking our using a thread-safe map implementation. – Jon Skeet Nov 24 '14 at 12:06
  • 1
    Agree on the practice of extending HashMap. This class is designed to map keys efficiently to values. Unless this is also the main purpose of your Subscriber class then extending HashMap suggests poor design. Remember, extends indicates an "is-a" relationship; unless it makes sense to say "Subscriber is a HashMap" then it's probably the wrong design. I suspect "Subscriber has a HashMap" is more appropriate. – BarrySW19 Nov 24 '14 at 12:54
  • @BarrySW19 In our case it does make sense. Since our list of objects is-a HashMap instance, this made our integration with XML-RPC and JSON a lot simpler, as we could just cast it directly to a map, and send it to the clients. For all other usage, I would agree! I liked your nice way of explaining it. – goddva Nov 24 '14 at 13:26

1 Answers1

1

Apart from threading issue there is a clear route to this result:

class Key implements CharSequence {

    private byte[] key;

    public Key(String key) {
        // Take a copy of the bytes of the string.
        this.key = key.getBytes();
    }

    @Override
    public int length() {
        return key.length;
    }

    @Override
    public char charAt(int index) {
        return (char) key[index];
    }

    @Override
    public CharSequence subSequence(int start, int end) {
        return new Key(new String(key).substring(start, end));
    }

    // Allow the key to change.
    public void setKey(String newValue) {
        key = newValue.getBytes();
    }

    @Override
    public String toString() {
        return new String(key);
    }
}

public void test() {
    Map<CharSequence, String> testMap = new HashMap<>();
    Key aKey = new Key("a");
    Key bKey = new Key("b");
    testMap.put(aKey, "a");
    testMap.put(bKey, "b");
    bKey.setKey("a");
    System.out.println(testMap.keySet());
}

This is essentially making the keys of the map mutable so they can be changed after they are added to the map.

Although this may not be the problem you are facing (a multi-threading issue is much more likely) this is a real answer to the question How come my HashMap have a duplicate key?.

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • it's an answer to the title of the question. In the screenshot, one can see that the key is a String, so it's not an answer to the full question ;-) – cello Nov 24 '14 at 12:25
  • Except the screenshot clearly shows that the type of the key is String. – JB Nizet Nov 24 '14 at 12:25