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.
Related
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!
I am iterating over two collections and check if both collections contain
the same elements. I can't use Java 8.
edit 1 year after:
I created the method in the question to check if two Collections contain the same elements, without thinking about the fact that I am passing two Collection implementations into the method.
But Collection does not determine how elements are sorted. And I am iterating over the collections. Thus, some implementation of Collection could save elements in random order, while containing the same elements.
Both collections contain elements that are comparable and the content
is defined as equal, if all elements return a x.compareTo(y) with 0.
Two values are defined as different, if one of them is null, but not the other.
I want to find an elegant way to compare on nullity and prevent
a null check on the final compareTo().
My current implementation:
public static <T extends Comparable<T>> boolean isSame(#Nullable Collection<T> a, #Nullable Collection<T> b) {
if (a == null || b == null) {
return (a == null && b == null);
}
if (a.size() != b.size()) {
return false;
}
Iterator<T> aIt = a.iterator();
Iterator<T> bIt = b.iterator();
while (aIt.hasNext()) {
T aValue = aIt.next();
T bValue = bIt.next();
if (aValue == null || bValue == null) {
if (aValue == null ^ bValue == null) {
return false;
}
//both null, don't compare, continue looping...
} else if (aValue.compareTo(bValue) != 0) {
return false;
}
}
return true;
}
I want to continue the while loop, if both values are null, because that is
defined as equal.
But I am struggling with this part:
if (aValue == null || bValue == null) {
if (aValue == null ^ bValue == null) {
return false;
}
}
Question:
Is there a more elegant and readable way to compare on nullity, do a further compare if both are not null, return false if only one is null, and continue the loop, if both values are null?
The sequence as follows should work well:
if(aValue == null && bValue == null) continue; // both null; continue
if(aValue == null || bValue == null) return false; // any null; return false
if(aValue.compareTo(bValue) != 0) { // both non-null; compare
return false;
}
In Java8, you can build a Comparator that would replace comparison sequence at cost of creating an extra object (you will need to decide if you care about that):
Comparator<T> cmp = Comparator.nullsLast(Comparator.naturalOrder());
The compararor will take care of null comparison for you (since you assume that two nulls are equal):
while (aIt.hasNext()) {
T aValue = aIt.next();
T bValue = bIt.next();
if (cmp.compare(aValue, bValue) != 0) {
return false;
}
}
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.
As we know, null is not allowed in Hashtable.
But when I checked the source code of Hashtable (jdk 1.8).
I only saw the check of value and couldn't find the key check.
Here is the source code below of the put method:
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
#SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
The key check is here:
int hash = key.hashCode();
This will throw a NullPointerException if the key is null.
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?