上一篇文章的传送门:关于一些基础的Java问题的解答(二)
public V put(K key, V value) { return putVal(key, value, false); }
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; }来看一些关键的代码:在第2行,方法检测了key和value是否为空,如果为空则抛出NullPointerException(看来线程安全的key和value都必须非空,和HashTable一样)。然后在第9行构造了一个Node对象,这个对象代表的是键值对节点(内部有next指针指向下一个节点)的实体。在ConcurrentHashMap内部维护了一个Node对象的数组,它大小是2的指数,且是volatile的具有原子可见性,数组索引是key经过哈希函数得出的哈希值:
/** * The array of bins. Lazily initialized upon first insertion. * Size is always a power of two. Accessed directly by iterators. */ transient volatile Node<K,V>[] table;看回putVal方法,从第12行的注释我们可以看出,如果是往一个空的索引位置放入一个新的Node节点,则不需要加锁。再看到方法第18行,我们发现这里有个临界区,此时处理的是往一个已有节点的索引位置加入新的节点情况,那么在链表完成之前很明显我们不应该让其他新节点干扰我们的工作,因此此处为索引头的Node对象加了锁,但此时别的索引位置是不加锁的。
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; }可以看到由于volatile关键字保证了原子可见性,get方法是完全没有加锁的。
/** * Stripped-down version of helper class used in previous version, * declared for the sake of serialization compatibility */ static class Segment<K,V> extends ReentrantLock implements Serializable { private static final long serialVersionUID = 2249069246763182397L; final float loadFactor; Segment(float lf) { this.loadFactor = lf; } }
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>其与HashMap最大的不同在于内部维护了一个用于遍历的双向链表,遍历的时候能够保持元素插入的顺序:
/** * The head (eldest) of the doubly linked list. */ transient LinkedHashMap.Entry<K,V> head; /** * The tail (youngest) of the doubly linked list. */ transient LinkedHashMap.Entry<K,V> tail;
/** * The comparator used to maintain order in this tree map, or * null if it uses the natural ordering of its keys. * * @serial */ private final Comparator<? super K> comparator; private transient Entry<K,V> root;
public class Test { public static void main(String[] args) { try { System.out.println("try"); return; } catch (Exception e) { } finally { System.out.println("finally"); } } }