ConcurrentHashMap where does get method lock? - java

I read this excellent article about the ConcurrentHashMap by Brian Goetz. But when I was looking at the code of ConcurrentHashMap in more recent java version (1.8), I noticed a couple of differences
The next pointer in the MapEntry is not final, but rather volatile so it is possible to modify the list in the middle and not just beginning.
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
Also I don't see where exactly the get method is obtaining the lock when it fails to lookup the key in the initial iteration
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
Can someone please explain how and where is the lock obtained in the get method, if at all?

Related

Cannot understand a line in HashMap.java code

I was going through HashMap.java and in the function getNode(...),
I came across three things I didn't understand:
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
Firstly, why are they comparing hashcode and key both? Should one comparison not be enough?
Secondly, why even store hashcode in the node when collision is possible.
Thirdly, this if condition :
(k = e.key) == key || (key != null && key.equals(k)))
seems like the part after the OR should be enough.
Can someone shed some light into this design decision please!

concurrentHashMap has a volatile table , why need unsafe.getObjectVolatile() when get()

like the title; i think unsafe.getObjectVolatile() when get() is unnecessary ,and volatile table could make sure thread get the element up to date
transient volatile Node<K,V>[] table;
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
There are two reasons:
The code is not accessing the value of table through the field table - rather, the reference is copied to a local variable called tab and accessed through that variable. Since that variable is not volatile (and local variables cannot be volatile), none of the memory model guarantees are granted.
The guarantees of volatile only apply if one thread A writes the volatile variable; other threads that read from the same volatile variable are guaranteed to then see all changes made by thread A. However, in this case, the variable table is not updated frequently - instead, the elements of the array to which table points are updated. But these elements are not volatile - in fact, Java does not support volatile array elements. But, with Unsafe.getObjectVolatile and Unsafe.putObjectVolatile, you can volatile semantics for array element access, event when you cannot directly declare an array of volatile elements in Java. And that's what is needed for this code.

why ConcurrentHashMap doesn't use 'CAS' but use 'synchronized' when tabAt[i] is not null

final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
When tabAt(tab, i = (n - 1) & hash)) is null, it use CAS to add/modify a node, but when is not null, it use synchronized (f). I think it can still use CAS of last node or node with key that need put of tabAt(tab, i = (n - 1) & hash)) to add/modify a node. But why not? My idea is wrong.

HashMap get method in Java 6 & Java 8

I am looking at HashMap get method in Java 6 & Java 8, the implementation in Java 8 is little complex, I am not able to get it.
This is from Java 6:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = 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.equals(k)))
return e.value;
}
return null;
}
Here in Java 6, it is getting the right Entry element and trying to find the corresponding value based on given key.
This code if from Java 8:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k)))) {
return first;
}
if ((e = first.next) != null) {
if (first instanceof TreeNode) {
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
}
do {
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
return e;
}
} while ((e = e.next) != null);
}
}
return null;
}
I am not able to understand the logic in Java 8.
How they are taking the first element:
(first = tab[(n - 1) & hash]) != null)
and what is this extra logic:
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
Regarding:
(first = tab[(n - 1) & hash]) != null)
That comes from how the entry is added to the table, shown below:
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
AND-ing (n-1) and hash allows entries with hashCode=hash to be spread over the n entries of the table. (n-1) is used to prevent the edge case of attempting to insert into tab[n] - which could lead to ArrayIndexOutOfBoundsException since tab.length is n.
The "extra logic" that you are referring to:
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
The above returns the very first Node from the table which not only matches the hashCode of the key being searched, but also exactly "equals" that key.
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
The above returns the Node if the bucket has been "Treeified" - details about which as pointed in one of the comments is specified in the "Implementation Notes" of this class.

How can i count comparisons are made when i try to enter a new key in a hash map?

I want to make a method to count how many comparisons are made when i want to put a new random key in my hash map . The code I used to put new keys in the map is the following :
public void put(int key, int value) {
int hash = (key % table.length);
int initialHash = -1;
int indexOfDeletedEntry = -1;
while (hash != initialHash
&& (table[hash] == DeletedEntry.getUniqueDeletedEntry()
|| table[hash] != null
&& table[hash].getKey() != key)) {
if (initialHash == -1)
initialHash = hash;
if (table[hash] == DeletedEntry.getUniqueDeletedEntry())
indexOfDeletedEntry = hash;
hash = (hash + 1) % table.length;
}
if ((table[hash] == null || hash == initialHash)
&& indexOfDeletedEntry != -1) {
table[indexOfDeletedEntry] = new HashEntry(key, value);
size++;
} else if (initialHash != hash)
if (table[hash] != DeletedEntry.getUniqueDeletedEntry()
&& table[hash] != null && table[hash].getKey() == key)
table[hash].setValue(value);
else {
table[hash] = new HashEntry(key, value);
size++;
}
if (size >= maxSize)
resize();
}
The class for the deleted entry is the following :
public class DeletedEntry extends HashEntry {
private static DeletedEntry entry = null;
private DeletedEntry() {
super(-1, -1);
}
public static DeletedEntry getUniqueDeletedEntry() {
if (entry == null)
entry = new DeletedEntry();
return entry;
}
}
Also , HashEntry class has 2 int variables , int key and int value .
Any Idea how i can count the comparisons ?
This is what I've done in my main:
Random rand = new Random();
int[] comparisons = new int[20];
int key = 0;
for (int k=0;k<20;k++){
key = rand.nextInt(1000) + 1;
}
(I'm assuming that this is a learning exercise of some kind. Hence advice to use or extend an existing Map implementation is irrelevant.)
The simple answer is that you increment a counter each time you "compare" keys. You could do that inline, or you could write yourself a little helper method like this:
private boolean compareKeys(int key1, int key2) {
count++;
return key1 == key2;
}
and then change your code to use this helper each time it compares keys; e.g.
while (hash != initialHash
&& (table[hash] == DeletedEntry.getUniqueDeletedEntry()
|| table[hash] != null
&& !compareKeys(table[hash].getKey(), key))) {
and
if (table[hash] != DeletedEntry.getUniqueDeletedEntry()
&& table[hash] != null
&& compareKeys(table[hash].getKey(), key))
There really is no clever solution to this problem.
You can write your own CustomHashMap
In this CustomHashMap you can implement a new put() method that keeps count of the comparisons and then returns that value.
public int put(int key, int value) {
int hash = (key % table.length);
int initialHash = -1;
int indexOfDeletedEntry = -1;
int numberOfComparisons = 1;
while (hash != initialHash
&& (table[hash] == DeletedEntry.getUniqueDeletedEntry()
|| table[hash] != null
&& table[hash].getKey() != key)) {
numberOfComparisons++;
if (initialHash == -1)
initialHash = hash;
if (table[hash] == DeletedEntry.getUniqueDeletedEntry())
indexOfDeletedEntry = hash;
hash = (hash + 1) % table.length;
}
if ((table[hash] == null || hash == initialHash)
&& indexOfDeletedEntry != -1) {
table[indexOfDeletedEntry] = new HashEntry(key, value);
size++;
} else if (initialHash != hash)
if (table[hash] != DeletedEntry.getUniqueDeletedEntry()
&& table[hash] != null && table[hash].getKey() == key)
table[hash].setValue(value);
else {
table[hash] = new HashEntry(key, value);
size++;
}
if (size >= maxSize)
resize();
return numberOfComparisons;
}

Categories