,

Friday 2 January 2015

HashMap and ConcurrentHashMap

Why use ConcurrentHashMap ?

In this practical tutorial we will try to understand the need of  ConcurrentHashMap. This topic is frequently asked in interviews. To understand the same we will work on a scenario.
To create a scenario where 3 threads are simultaneously inserting the 13th element I have copied AbstractMap and HashMap with few modifications of Sysout and removed few unrequired functions. HashMap has default 16 buckets(Entry [] table) and has 0.75 loadfactor that means threashold of 12 elements.While inserting the 13th element , the number of buckets is doubled(16 becomes 32) and hashing happens again to find the bucket number for hash & 31. Purposely I have implemented hashcode in such a way that it will insert all the Employee objects in one bucket and when the HashMap is resized  (doubled) , it will insert the available elements in reversed order using the transfer function of HashMap.
We have 3 java files AbstractMap.java , HashMap.java and MainClass.java.
(Copy the same in Eclipse and run )

Note: I have tried to have a practical justification for the need of ConcurrentHashMap using Mutlithreading , the program has more lines of code as I have copied and modified few API classes for output purpose.The modified and added syntax is with different background and text colour.

Demo 1:


//AbstractMap.java
package com.a.b;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public abstract class AbstractMap<K, V> implements Map<K, V> {
protected AbstractMap() {
}
public int size() {
return entrySet().size();
}
public boolean isEmpty() {
return size() == 0;
}
public boolean containsValue(Object value) {
Iterator<Entry<K, V>> i = entrySet().iterator();
if (value == null) {
while (i.hasNext()) {
Entry<K, V> e = i.next();
if (e.getValue() == null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K, V> e = i.next();
if (value.equals(e.getValue()))
return true;
}
}
return false;
}
public boolean containsKey(Object key) {
Iterator<Map.Entry<K, V>> i = entrySet().iterator();
if (key == null) {
while (i.hasNext()) {
Entry<K, V> e = i.next();
if (e.getKey() == null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K, V> e = i.next();
if (key.equals(e.getKey()))
return true;
}
}
return false;
}
public V get(Object key) {
Iterator<Entry<K, V>> i = entrySet().iterator();
if (key == null) {
while (i.hasNext()) {
Entry<K, V> e = i.next();
if (e.getKey() == null)
return e.getValue();
}
} else {
while (i.hasNext()) {
Entry<K, V> e = i.next();
if (key.equals(e.getKey()))
return e.getValue();
}
}
return null;
}
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
public V remove(Object key) {
Iterator<Entry<K, V>> i = entrySet().iterator();
Entry<K, V> correctEntry = null;
if (key == null) {
while (correctEntry == null && i.hasNext()) {
Entry<K, V> e = i.next();
if (e.getKey() == null)
correctEntry = e;
}
} else {
while (correctEntry == null && i.hasNext()) {
Entry<K, V> e = i.next();
if (key.equals(e.getKey()))
correctEntry = e;
}
}
V oldValue = null;
if (correctEntry != null) {
oldValue = correctEntry.getValue();
i.remove();
}
return oldValue;
}
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
public void clear() {
entrySet().clear();
}
transient volatile Set<K> keySet = null;
transient volatile Collection<V> values = null;
public Set<K> keySet() {
if (keySet == null) {
keySet = new AbstractSet<K>() {
public Iterator<K> iterator() {
return new Iterator<K>() {
private Iterator<Entry<K, V>> i = entrySet().iterator();
public boolean hasNext() {
return i.hasNext();
}
public K next() {
return i.next().getKey();
}
public void remove() {
i.remove();
}
};
}
public int size() {
return AbstractMap.this.size();
}
public boolean contains(Object k) {
return AbstractMap.this.containsKey(k);
}
};
}
return keySet;
}
public Collection<V> values() {
if (values == null) {
values = new AbstractCollection<V>() {
public Iterator<V> iterator() {
return new Iterator<V>() {
private Iterator<Entry<K, V>> i = entrySet().iterator();
public boolean hasNext() {
return i.hasNext();
}
public V next() {
return i.next().getValue();
}
public void remove() {
i.remove();
}
};
}
public int size() {
return AbstractMap.this.size();
}
public boolean contains(Object v) {
return AbstractMap.this.containsValue(v);
}
};
}
return values;
}
public abstract Set<Entry<K, V>> entrySet();
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<K, V> m = (Map<K, V>) o;
if (m.size() != size())
return false;
try {
Iterator<Entry<K, V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K, V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key) == null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
public int hashCode() {
int h = 0;
Iterator<Entry<K, V>> i = entrySet().iterator();
while (i.hasNext())
h += i.next().hashCode();
return h;
}
public String toString() {
Iterator<Entry<K, V>> i = entrySet().iterator();
if (!i.hasNext())
return "{}";
StringBuilder sb = new StringBuilder();
sb.append('{');
for (;;) {
Entry<K, V> e = i.next();
K key = e.getKey();
V value = e.getValue();
sb.append(key == this ? "(this Map)" : key);
sb.append('=');
sb.append(value == this ? "(this Map)" : value);
if (!i.hasNext())
return sb.append('}').toString();
sb.append(", ");
}
}
protected Object clone() throws CloneNotSupportedException {
AbstractMap<K, V> result = (AbstractMap<K, V>) super.clone();
result.keySet = null;
result.values = null;
return result;
}
private static boolean eq(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}
public static class SimpleEntry<K, V> implements Entry<K, V>,
java.io.Serializable {
private static final long serialVersionUID = -8499721149061103585L;
private final K key;
private V value;

public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
public SimpleEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry) o;
return eq(key, e.getKey()) && eq(value, e.getValue());
}
public int hashCode() {
return (key == null ? 0 : key.hashCode())
^ (value == null ? 0 : value.hashCode());
}
public String toString() {
return key + "=" + value;
}
}
public static class SimpleImmutableEntry<K, V> implements Entry<K, V>,
java.io.Serializable {
private static final long serialVersionUID = 7138329143949025153L;
private final K key;
private final V value;
public SimpleImmutableEntry(K key, V value) {
this.key = key;
this.value = value;
}
public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
throw new UnsupportedOperationException();
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry) o;
return eq(key, e.getKey()) && eq(value, e.getValue());
}
public int hashCode() {
return (key == null ? 0 : key.hashCode())
^ (value == null ? 0 : value.hashCode());
}
public String toString() {
return key + "=" + value;
}
}
}
//HashMap.java
package com.a.b;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>,
Cloneable, Serializable {
static final int DEFAULT_INITIAL_CAPACITY = 16;
static final int MAXIMUM_CAPACITY = 1 << 30;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
transient Entry[] table;
transient int size;
int threshold;
final float loadFactor;
transient volatile int modCount;
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: "
+ initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: "
+ loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int) (capacity * loadFactor);
table = new Entry[capacity];
init();
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int) (DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
}
// internal utilities
void init() {
}
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length - 1);
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
final Entry<K, V> getEntry(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode());
for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
Object k;
if (e.hash == hash
&& ((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
public V put(K key, V value) {
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K, V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
void resize(int newCapacity) {
System.out
.println("resize----------------------------------------------------started");
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int) (newCapacity * loadFactor);
}
void transfer(Entry[] newTable) {
int count = 0;
Entry[] src = table;
int newCapacity = newTable.length;
System.out.println("newCapacity : --==--" + newCapacity);
for (int j = 0; j < src.length; j++) {
Entry<K, V> e = src[j];
if (e != null) {
src[j] = null;
do {
count++;
Entry<K, V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
System.out.println("data: " + newTable[i] + " "
+ Thread.currentThread().getName());
if (count == 5) {
try {
Thread.sleep(5000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
} while (e != null);
}
}
}
final Entry<K, V> removeMapping(Object o) {
if (!(o instanceof Map.Entry))
return null;
Map.Entry<K, V> entry = (Map.Entry<K, V>) o;
Object key = entry.getKey();
int hash = (key == null) ? 0 : hash(key.hashCode());
int i = indexFor(hash, table.length);
Entry<K, V> prev = table[i];
Entry<K, V> e = prev;
while (e != null) {
Entry<K, V> next = e.next;
if (e.hash == hash && e.equals(entry)) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
static class Entry<K, V> implements Map.Entry<K, V> {
final K key;
V value;
Entry<K, V> next;
final int hash;
Entry(int h, K k, V v, Entry<K, V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry) o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return (key == null ? 0 : key.hashCode())
^ (value == null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
void recordAccess(HashMap<K, V> m) {
}
void recordRemoval(HashMap<K, V> m) {
}
}
void addEntry(int hash, K key, V value, int bucketIndex) {
System.out.println("inside add entry: "
+ Thread.currentThread().getName());
Entry<K, V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K, V>(hash, key, value, e);
if (size++ >= threshold) {
System.out.println("Size ========== " + size);
resize(2 * table.length);
}
}
private abstract class HashIterator<E> implements Iterator<E> {
Entry<K, V> next; // next entry to return
int expectedModCount; // For fast-fail
int index; // current slot
Entry<K, V> current; // current entry
HashIterator() {
expectedModCount = modCount;
if (size > 0) { // advance to first entry
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
}
public final boolean hasNext() {
return next != null;
}
final Entry<K, V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K, V> e = current = next;
if (e == null)
throw new NoSuchElementException();
if ((next = e.next) == null) {
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null) ;
}
return e;
}
public void remove() { }
}
private final class ValueIterator extends HashIterator<V> {
public V next() {
return nextEntry().value;
}
}
private final class KeyIterator extends HashIterator<K> {
public K next() {
return nextEntry().getKey();
}
}
private final class EntryIterator extends HashIterator<Map.Entry<K, V>> {
public Map.Entry<K, V> next() {
return nextEntry();
}
}
// Subclass overrides these to alter behavior of views' iterator() method
Iterator<K> newKeyIterator() {
return new KeyIterator();
}
Iterator<V> newValueIterator() {
return new ValueIterator();
}
Iterator<Map.Entry<K, V>> newEntryIterator() {
return new EntryIterator();
}
private transient Set<Map.Entry<K, V>> entrySet = null;
public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
private final class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
}
public Collection<V> values() {
Collection<V> vs = values;
return (vs != null ? vs : (values = new Values()));
}
private final class Values extends AbstractCollection<V> {
public Iterator<V> iterator() {
return newValueIterator();
}
public int size() {
return size;
}
}
public Set<Map.Entry<K, V>> entrySet() {
return entrySet0();
}
private Set<Map.Entry<K, V>> entrySet0() {
Set<Map.Entry<K, V>> es = entrySet;
return es != null ? es : (entrySet = new EntrySet());
}
private final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
public Iterator<Map.Entry<K, V>> iterator() {
return newEntryIterator();
}
public int size() {
return size;
}
}
private static final long serialVersionUID = 362498820763181265L;
int capacity() {
return table.length;
}
float loadFactor() {
return loadFactor;
}
}

//MainClass.java
package com.a.b;
import java.util.concurrent.TimeUnit;
class Employee {
private int id;
private String name;
public Employee(int id, String name) {
super();
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + "]";
}
@Override
public int hashCode() {
// final int prime = 31;
// int result = 1;
// result = prime * result + id;
// result = prime * result + ((name == null) ? 0 : name.hashCode());
return 1;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
class PutinMap {
HashMap<Employee, Integer> map;
public PutinMap(HashMap<Employee, Integer> map) {
super();
this.map = map;
}
public void doPut(Employee e, Integer data) {
map.put(e, data);
}
}
public class MainClass {
public static void main(String[] args) throws InterruptedException {
System.out.println("Main Started ");
final Employee emp13 = new Employee(13, "Jeniffer");
final Employee emp14 = new Employee(14, "Linda");
final Employee emp15 = new Employee(15, "Abdul");
  HashMap<Employee, Integer> map = new HashMap<Employee, Integer>(16);
Employee emp;
for (int i = 1; i <= 12; i++) {
emp = new Employee(i, i + "name");
map.put(emp, i);
}
System.out.println(map);
final PutinMap object = new PutinMap(map);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("before 13 put");
object.doPut(emp13, 13);
System.out.println("after 13 put");
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("before 14 put");
object.doPut(emp14, 14);
System.out.println("after 14 put");
}
});
t2.start();
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("before 15 put");
object.doPut(emp15, 15);
System.out.println("after 15 put");
}
});
t3.start();
//Thread.sleep(3000);
t1.join();
t2.join();
t3.join();
System.out.println("final" + map);
System.out
.println("After 3 simultaneous put the size is : " + map.size);
}
}



Output:
Possible first Output

Main Started
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
{Employee [id=12, name=12name]=12, Employee [id=11, name=11name]=11, Employee [id=10, name=10name]=10,Employee [id=9, name=9name]=9, Employee [id=8, name=8name]=8, Employee [id=7, name=7name]=7, Employee[id=6, name=6name]=6, Employee [id=5, name=5name]=5, Employee [id=4, name=4name]=4, Employee [id=3,name=3name]=3, Employee [id=2, name=2name]=2, Employee [id=1, name=1name]=1}
before 13 put
inside add entry: Thread-0
Size ========== 13
resize----------------------------------------------------started
newCapacity : --==--32
data: Employee [id=13, name=Jeniffer]=13 Thread-0
data: Employee [id=12, name=12name]=12 Thread-0
data: Employee [id=11, name=11name]=11 Thread-0
data: Employee [id=10, name=10name]=10 Thread-0
data: Employee [id=9, name=9name]=9 Thread-0
before 14 put
inside add entry: Thread-1
Size ========== 14
resize----------------------------------------------------started
newCapacity : --==--32
data: Employee [id=14, name=Linda]=14 Thread-1
after 14 put
before 15 put
inside add entry: Thread-2
after 15 put
data: Employee [id=8, name=8name]=8 Thread-0
data: Employee [id=7, name=7name]=7 Thread-0
data: Employee [id=6, name=6name]=6 Thread-0
data: Employee [id=5, name=5name]=5 Thread-0
data: Employee [id=4, name=4name]=4 Thread-0
data: Employee [id=3, name=3name]=3 Thread-0
data: Employee [id=2, name=2name]=2 Thread-0
data: Employee [id=1, name=1name]=1 Thread-0
after 13 put
final{Employee [id=1, name=1name]=1, Employee [id=2, name=2name]=2, Employee [id=3, name=3name]=3,Employee [id=4, name=4name]=4, Employee [id=5, name=5name]=5, Employee [id=6, name=6name]=6, Employee[id=7, name=7name]=7, Employee [id=8, name=8name]=8, Employee [id=9, name=9name]=9, Employee [id=10,name=10name]=10, Employee [id=11, name=11name]=11, Employee [id=12, name=12name]=12, Employee [id=13,name=Jeniffer]=13}
After 3 simultaneous put the size is : 15

Possible second output

Main Started
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
inside add entry: main
{Employee [id=12, name=12name]=12, Employee [id=11, name=11name]=11, Employee [id=10, name=10name]=10,Employee [id=9, name=9name]=9, Employee [id=8, name=8name]=8, Employee [id=7, name=7name]=7, Employee[id=6, name=6name]=6, Employee [id=5, name=5name]=5, Employee [id=4, name=4name]=4, Employee [id=3,name=3name]=3, Employee [id=2, name=2name]=2, Employee [id=1, name=1name]=1}
before 14 put
inside add entry: Thread-1
Size ========== 13
resize----------------------------------------------------started
newCapacity : --==--32
data: Employee [id=14, name=Linda]=14 Thread-1
data: Employee [id=12, name=12name]=12 Thread-1
data: Employee [id=11, name=11name]=11 Thread-1
before 13 put
inside add entry: Thread-0
Size ========== 14
resize----------------------------------------------------started
newCapacity : --==--32
data: Employee [id=13, name=Jeniffer]=13 Thread-0
after 13 put
data: Employee [id=10, name=10name]=10 Thread-1
before 15 put
inside add entry: Thread-2
after 15 put
data: Employee [id=9, name=9name]=9 Thread-1
data: Employee [id=8, name=8name]=8 Thread-1
data: Employee [id=7, name=7name]=7 Thread-1
data: Employee [id=6, name=6name]=6 Thread-1
data: Employee [id=5, name=5name]=5 Thread-1
data: Employee [id=4, name=4name]=4 Thread-1
data: Employee [id=3, name=3name]=3 Thread-1
data: Employee [id=2, name=2name]=2 Thread-1
data: Employee [id=1, name=1name]=1 Thread-1
after 14 put
final{Employee [id=1, name=1name]=1, Employee [id=2, name=2name]=2, Employee [id=3, name=3name]=3,Employee [id=4, name=4name]=4, Employee [id=5, name=5name]=5, Employee [id=6, name=6name]=6, Employee[id=7, name=7name]=7, Employee [id=8, name=8name]=8, Employee [id=9, name=9name]=9, Employee [id=10,name=10name]=10, Employee [id=11, name=11name]=11, Employee [id=12, name=12name]=12, Employee [id=14,name=Linda]=14}
After 3 simultaneous put the size is : 15


From the above two outputs , it is observed that employee with id 13 can be added or the one with 14 can also be added , we cannot guarantee this as threads are scheduled by OS. Two threads have resized the HashMap from 16 to 32 , but only one ends up in updating the data.The output has 13 elements but has the size fifteen as three threads have updated the variable size. So that is the reason why it is suggested to use ConcurrentHashMap when you have multiple threads affecting Hashmap. 

Second Demo speaks about ConcurrentModificationException due to multiple threads modifying the HashMap.

Note: Sometimes the program will execute fine and sometimes it will throw Exception.

Demo 2:

package com.a.b.c;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
class Employee {
private int id;
private String name;
public Employee(int id, String name) {
super();
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + "]";
}
@Override
public int hashCode() {
// final int prime = 31;
// int result = 1;
// result = prime * result + id;
// result = prime * result + ((name == null) ? 0 : name.hashCode());
return 1;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
class PutinMap {
HashMap<Employee, Integer> map;
public PutinMap(HashMap<Employee, Integer> map) {
super();
this.map = map;
}
public void doPut(Employee e, Integer data) {
map.put(e, data);
System.out.println(map);
}
}
public class MainClass {
public static void main(String[] args) throws InterruptedException {
System.out.println("Main Started ");
final Employee emp13 = new Employee(13, "Jeniffer");
final Employee emp14 = new Employee(14, "Linda");
final Employee emp15 = new Employee(15, "Abdul");
HashMap<Employee, Integer> map = new HashMap<Employee, Integer>(16);
Employee emp;
for (int i = 1; i <= 12; i++) {
emp = new Employee(i, i + "name");
map.put(emp, i);
}
System.out.println(map);
final PutinMap object = new PutinMap(map);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("before 13 put");
object.doPut(emp13, 13);
System.out.println("after 13 put");
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("before 14 put");
object.doPut(emp14, 14);
System.out.println("after 14 put");
}
});
t2.start();
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("before 15 put");
object.doPut(emp15, 15);
System.out.println("after 15 put");
}
});
t3.start();
// Thread.sleep(3000);
t1.join();
t2.join();
t3.join();
System.out.println("final" + map);
}
}


Output
Possible Output 1:

Main Started
{Employee [id=12, name=12name]=12, Employee [id=11, name=11name]=11, Employee [id=10, name=10name]=10,Employee [id=9, name=9name]=9, Employee [id=8, name=8name]=8, Employee [id=7, name=7name]=7, Employee[id=6, name=6name]=6, Employee [id=5, name=5name]=5, Employee [id=4, name=4name]=4, Employee [id=3,name=3name]=3, Employee [id=2, name=2name]=2, Employee [id=1, name=1name]=1}
before 13 put
{Employee [id=1, name=1name]=1, Employee [id=2, name=2name]=2, Employee [id=3, name=3name]=3, Employee[id=4, name=4name]=4, Employee [id=5, name=5name]=5, Employee [id=6, name=6name]=6, Employee [id=7,name=7name]=7, Employee [id=8, name=8name]=8, Employee [id=9, name=9name]=9, Employee [id=10,name=10name]=10, Employee [id=11, name=11name]=11, Employee [id=12, name=12name]=12, Employee [id=13,name=Jeniffer]=13}
after 13 put
before 14 put
before 15 put
{Employee [id=15, name=Abdul]=15, Employee [id=14, name=Linda]=14, Employee [id=1, name=1name]=1,Employee [id=2, name=2name]=2, Employee [id=3, name=3name]=3, Employee [id=4, name=4name]=4, Employee[id=5, name=5name]=5, Employee [id=6, name=6name]=6, Employee [id=7, name=7name]=7, Employee [id=8,name=8name]=8, Employee [id=9, name=9name]=9, Employee [id=10, name=10name]=10, Employee [id=11,name=11name]=11, Employee [id=12, name=12name]=12, Employee [id=13, name=Jeniffer]=13}
after 15 put
Exception in thread "Thread-1" java.util.ConcurrentModificationException
final{Employee [id=15, name=Abdul]=15, Employee [id=14, name=Linda]=14, Employee [id=1, name=1name]=1,Employee [id=2, name=2name]=2, Employee [id=3, name=3name]=3, Employee [id=4, name=4name]=4, Employee[id=5, name=5name]=5, Employee [id=6, name=6name]=6, Employee [id=7, name=7name]=7, Employee [id=8,name=8name]=8, Employee [id=9, name=9name]=9, Employee [id=10, name=10name]=10, Employee [id=11,name=11name]=11, Employee [id=12, name=12name]=12, Employee [id=13, name=Jeniffer]=13}
at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
at java.util.HashMap$EntryIterator.next(Unknown Source)
at java.util.HashMap$EntryIterator.next(Unknown Source)
at java.util.AbstractMap.toString(Unknown Source)
at java.lang.String.valueOf(Unknown Source)
at java.io.PrintStream.println(Unknown Source)
at com.a.b.c.PutinMap.doPut(MainClass.java:76)
at com.a.b.c.MainClass$2.run(MainClass.java:113)
at java.lang.Thread.run(Unknown Source)

Possible output 2:

Main Started
{Employee [id=12, name=12name]=12, Employee [id=11, name=11name]=11, Employee [id=10, name=10name]=10,Employee [id=9, name=9name]=9, Employee [id=8, name=8name]=8, Employee [id=7, name=7name]=7, Employee[id=6, name=6name]=6, Employee [id=5, name=5name]=5, Employee [id=4, name=4name]=4, Employee [id=3,name=3name]=3, Employee [id=2, name=2name]=2, Employee [id=1, name=1name]=1}
before 13 put
{Employee [id=1, name=1name]=1, Employee [id=2, name=2name]=2, Employee [id=3, name=3name]=3, Employee[id=4, name=4name]=4, Employee [id=5, name=5name]=5, Employee [id=6, name=6name]=6, Employee [id=7,name=7name]=7, Employee [id=8, name=8name]=8, Employee [id=9, name=9name]=9, Employee [id=10,name=10name]=10, Employee [id=11, name=11name]=11, Employee [id=12, name=12name]=12, Employee [id=13,name=Jeniffer]=13}
after 13 put
before 14 put
{Employee [id=14, name=Linda]=14, Employee [id=1, name=1name]=1, Employee [id=2, name=2name]=2,Employee [id=3, name=3name]=3, Employee [id=4, name=4name]=4, Employee [id=5, name=5name]=5, Employee[id=6, name=6name]=6, Employee [id=7, name=7name]=7, Employee [id=8, name=8name]=8, Employee [id=9,name=9name]=9, Employee [id=10, name=10name]=10, Employee [id=11, name=11name]=11, Employee [id=12,name=12name]=12, Employee [id=13, name=Jeniffer]=13}
before 15 put
after 14 put
{Employee [id=15, name=Abdul]=15, Employee [id=14, name=Linda]=14, Employee [id=1, name=1name]=1,Employee [id=2, name=2name]=2, Employee [id=3, name=3name]=3, Employee [id=4, name=4name]=4, Employee[id=5, name=5name]=5, Employee [id=6, name=6name]=6, Employee [id=7, name=7name]=7, Employee [id=8,name=8name]=8, Employee [id=9, name=9name]=9, Employee [id=10, name=10name]=10, Employee [id=11,name=11name]=11, Employee [id=12, name=12name]=12, Employee [id=13, name=Jeniffer]=13}
after 15 put
final{Employee [id=15, name=Abdul]=15, Employee [id=14, name=Linda]=14, Employee [id=1, name=1name]=1,Employee [id=2, name=2name]=2, Employee [id=3, name=3name]=3, Employee [id=4, name=4name]=4, Employee[id=5, name=5name]=5, Employee [id=6, name=6name]=6, Employee [id=7, name=7name]=7, Employee [id=8,name=8name]=8, Employee [id=9, name=9name]=9, Employee [id=10, name=10name]=10, Employee [id=11,name=11name]=11, Employee [id=12, name=12name]=12, Employee [id=13, name=Jeniffer]=13}

One of the output is throwing ConcurrentModification exception from toString function of HashMap where it uses Iterator to iterate and finds that the base data has been modified by other thread and that is why it throws the Exception. These two demos are good demos for THREAD INTERFERENCE and MEMORY INCONSISTENCY.

Conclusion: HashMap is not Threadsafe and HashMap is not suitable in environment where you have multiple threads affecting or modifying the base HashMap. In multithreaded environment  , it is suggested to use ConcurrentHashMap which works on the concepts of Segments along with bucket.



No comments:

Post a Comment