总览
1、ConcurrentHashMap基础介绍
线程安全版本的hashmap,在多线程环境下通过加锁来保证线程安全,相比于同样线程安全的HashTable,有更细粒度的锁,提高并发性能;基础的API操作同HashMap一致,底层锁机制保证了并发环境下的线程安全特性。
2、源码相关重要内部类介绍
为了更好理解源码的细节,需要对源码关键API涉及到的内部类、属性、方法做前置的介绍;
2.1、内部类介绍:
static class Node<K,V> implements Map.Entry<K,V> {
// node节点的hash值
final int hash;
// 节点对应的key
final K key;
// 节点对应的value
volatile V val;
// 保存后一个节点的引用
volatile Node<K,V> next;
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.val = val;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return val; }
public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
public final String toString(){ return key + "=" + val; }
// 不允许修改node节点的对应的值
public final V setValue(V value) {
throw new UnsupportedOperationException();
}
// 重写equals方法,key、value不为null,并且key和value与当前node相等。
public final boolean equals(Object o) {
Object k, v, u; Map.Entry<?,?> e;
return ((o instanceof Map.Entry) &&
(k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
(v = e.getValue()) != null &&
(k == key || k.equals(key)) &&
(v == (u = val) || v.equals(u)));
}
// 传入一个hash值和对象K,如果当前对象的hash值与传入的h相等,并且key值与传入的K相等,
// 则返回当前node对象,否则继续下一轮遍历,直到末尾,返回null,
// 即查找指定hash码和相同key的节点并返回、不存在返回null
Node<K,V> find(int h, Object k) {
Node<K,V> e = this;
if (k != null) {
do {
K ek;
if (e.hash == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
} while ((e = e.next) != null);
}
return null;
}
}
2.2、 属性介绍
// Node节点数组存放所有的元素
transient volatile Node<K,V>[] table;
// 用来控制初始化、扩容的状态标识,小于0代表当前对象正在扩容或者初始化,
// 为正数代表当前对象初始化后的大小
private transient volatile int sizeCtl;
2.3、方法介绍
// 1、initTable()用来初始化化table数组
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
// 判断当前的table数组为null,且或者长度为0时执行循环体
while ((tab = table) == null || tab.length == 0) {
// 如果当前对象状态sizeCtl小于0,则在处于初始化或者扩容状态
if ((sc = sizeCtl) < 0)
// 执行yield,让出cpu的执行权
Thread.yield(); // lost initialization race; just spin
// 此时如果没有其他线程在执行扩容和初始化,那么通过CAS操作将当前sizeCtl的值
// 修改成-1,代表当前线程正在初始化;如果别的线程也进来将让出CPU因为此时已经成-1
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 再次判断table数组是否为null,或者长度为0
if ((tab = table) == null || tab.length == 0) {
// 如果sc有初始值,则按照给定初始值进行初始化,否则默认大小
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
// 创建指定大小的node数组
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
// 进行赋值给当前对象table属性和tab
table = tab = nt;
// sc的大小变成原先成的3/4,即阈值变成新容量的0.75
sc = n - (n >>> 2);
}
} finally {
// 新容量的值赋值给状态标志
sizeCtl = sc;
}
break;
}
}
// 返回,扩容过程结束
return tab;
}
// 2、casTabAt()方法cas判断当前tab对象该位置的对象为c,则更改为v
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
// 3、helpTransfer()方法 帮助正在转移的线程进行数据转移
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
int rs = resizeStamp(tab.length);
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
// 4、putTreeVal()红黑树结构的数据插入,返回TreeNode节点
```java
final TreeNode<K,V> putTreeVal(int h, K k, V v) {
Class<?> kc = null;
// 插入操作,查找标识为false
boolean searched = false;
// 红黑树根节点赋值给p,开始自旋
for (TreeNode<K,V> p = root;;) {
int dir, ph; K pk;
// 若根节点为null,空树,则直接执行插入
if (p == null) {
// 根据传入的hash,k,v初始化TreeNode节点,赋值给root和first,跳出自旋
first = root = new TreeNode<K,V>(h, k, v, null, null);
break;
}
// 如果头结点不为null,且头结点的hash值大于待传入的hash值
else if ((ph = p.hash) > h)
// 令dir为-1
dir = -1;
// 如果头结点hash值小于待传入hash值
else if (ph < h)
// 令dir=1,正负数来表示插入的数据大小
dir = 1;
// 如果根节点的key和传入的key相等
else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
// 直接返回跟元素
return p;
// kc为null,comparableClassFor(k)返回传入key的比较器类型赋值给kc为null
// 或compareComparables(kc, k, pk))比较k和pk的大小,根据kc比较器规则为0
// 上述条件都属于不能够按照key比较排序的情形,要么传入空比较器,要么就是两个
// key没有实现同一比较器类型,或者头部的pk为null
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
// 插入操作,进来的时候,search已经置为false
if (!searched) {
// 声明q,ch节点,改search为true值
TreeNode<K,V> q, ch;
searched = true;
// 获取根节点的左子节点赋值给ch,不为null
// ch.findTreeNode()从左子节点开始遍历找到h,k相等的节点,找不到则为null
// 如果在左子树找到了或者在右子树找到了,那么就直接返回该节点。
if (((ch = p.left) != null &&
(q = ch.findTreeNode(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.findTreeNode(h, k, kc)) != null))
return q;
}
// 直接比较传入的k和根部节点的pk,比较hash值,如果小于pk,返回-1,否则返回1
dir = tieBreakOrder(k, pk);
}
// 走到这意味上面没有找到指定的节点,将根节点赋值给xp
TreeNode<K,V> xp = p;
// 如果dir小于0,将左节点给p,否则右节点给p,如果等于null执行if逻辑
if ((p = (dir <= 0) ? p.left : p.right) == null) {
// 根节点first赋值给f
TreeNode<K,V> x, f = first;
// 初始化节点赋值给x和first节点
first = x = new TreeNode<K,V>(h, k, v, f, xp);
if (f != null)
// f不为null,将x节点的值赋值给f节点前置指针指向x
f.prev = x;
if (dir <= 0)
// 如果dir<0,将当前x作为父节点的左孩子,否则为右孩子
xp.left = x;
else
xp.right = x;
// 如果父节点为黑色,将父节点改为红色
if (!xp.red)
x.red = true
else {
// 如果为红色,则锁定树,将锁定状态改为写入模式
lockRoot();
try {
// 开始调整红黑树结构,此处不展开,参考上一篇hashmap红黑树调整的逻辑处理
root = balanceInsertion(root, x);
} finally {
// 解除锁定,更改lockState=0
unlockRoot();
}
}
break;
}
}
// 验证红黑树的准确性,判断父节点和左右子节点的指向是否满足规则,色块是否满足红黑树规则
assert checkInvariants(root);
// 最后执行到这,插入成功,直接返回null
return null;
}
3、源码部分详解
下面对于ConcurrentHashMap的介绍从常用的API入手,包括增加、删除、更新、查询、扩容操作。
3.1 put() 添加元素
源码部分对key和value都标注了@NotNull注解,
表示put操作不允许null key和null value
public V put(K key, V value) {
return putVal(key, value, false);
}
继续调用putVal()方法
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 判断key或者value为null值,直接抛出空指针异常
if (key == null || value == null) throw new NullPointerException();
// hashcode方法作用,获取key对应的hash码,属于native方法
// spread方法作用,将key对应的hash码高16位和低16位进行异或,结果与HASH_BITS按位与
// 该做法保证最后的结果是正数,hash_bits=0x7fffffff,保证最高位结果一定是0也就是一定为正
int hash = spread(key.hashCode());
// 用来记录插入的个数
int binCount = 0;
// 当前table数组赋值给tab的node数组
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 如果tab数组位null,或者数组长度为0,那么执行初始化
if (tab == null || (n = tab.length) == 0)
// 进行初始化,初始化细节见2.3部分
tab = initTable();
// 如果不为空,并且当前hash值对应的数组位置的元素f不为null
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// cas判断i位置头部元素四是否为null,是则修改为一个Node元素;
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// 如果该位置不为空,则判断该元素的hash值是否为MOVED=-1,是则表示正在转移
else if ((fh = f.hash) == MOVED)
// 则帮助进行数据转移
tab = helpTransfer(tab, f);
else {
// 声明酒值为null
V oldVal = null;
// 头部节点加锁
synchronized (f) {
// 再次判断i位置的头部节点是否为f
if (tabAt(tab, i) == f) {
// f头结点的hash值大于0
if (fh >= 0) {
// 插入的个数变成1
binCount = 1;
// 头结点赋值给e,个数累加
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 待插入hash值和当前遍历节点e的hash值相等,且key相等
// 存在相同key,则是一次更新操作
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
// 将当前的value值赋值给旧值,因为要完成一个更新操作
oldVal = e.val;
// 此时传入的是false,那么允许更新
// onlyIfAbsent字面意思是仅不存在更新,true的话将不更新操作,
// 只有在false的时候,代表存在也可以更新
if (!onlyIfAbsent)
e.val = value;
break;
}
// 走到这儿代表不存在相同key,保留当前节点e赋值给pred
Node<K,V> pred = e;
// e向下寻找next,为空的话,则以传入的hash,key,value初始
// 化一个node节点,放在pred的后面,可见是尾部插入
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 如果是头节点的类型属于树类型,那么此时为红黑树结构
else if (f instanceof TreeBin) {
// f头节点属于树类型,将binCount的值改为2,存在一个和待插入一个
Node<K,V> p;
binCount = 2;
// 执行TreeBin的putTreeVal方法,如果返回值不为null,代表找到了,相同key
// 的节点,则返回该节点的value值,并赋值给oldVal,
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
// 此时传入为false,更新红黑树结构对应节点的value值
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
// binCount记录了遍历了的节点数,如果节点数不为0
if (binCount != 0) {
// 判断数量是否大于树化阈值8,如果满足
if (binCount >= TREEIFY_THRESHOLD)
// tab数组,从i位置开始进行树化,判断是否满足最小树化的容量标注
// 对该位置加锁进行树形化,
treeifyBin(tab, i);
if (oldVal != null)
// 如果旧值不为null,返回旧值,所以插入操作,会返回旧的值
return oldVal;
break;
}
}
}
// 插入一个元素,是否要选择扩容;
addCount(1L, binCount);
return null;
}
3.2、remove删除元素
源码对于key标识了@NotNull注解,表示要删除的key不能为null
public V remove(Object key) {
return replaceNode(key, null, null);
}
继续调用replaceNode()方法
final V replaceNode(Object key, V value, Object cv) {
// 获取key的hash码
int hash = spread(key.hashCode());
// 自旋,将table数组赋值给tab
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// tab为空,或者长度为0,hash值所对应位置的头部节点为空的情况跳出循环
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
// 如果头结点的hash值表示正在转移,则当前线程帮助转移数据
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
boolean validated = false;
// 头节点加锁
synchronized (f) {
// 加锁后再判断i位置对应的头部元素是否还是和f相等
if (tabAt(tab, i) == f) {
// 头节点hash值大于0
if (fh >= 0) {
validated = true;
// node头部节点赋值给e,前置节点pred为null,自旋
for (Node<K,V> e = f, pred = null;;) {
K ek;
// 如果找到hash和key相等的当前节点e
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
// 当前节点的value值给到ev
V ev = e.val;
// 此时传入的cv为null,或者传入的cv和当前节点的value值相等
// 或者ev不为null,cv和ev相等
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
// 把ev的值给到旧值oldVal
oldVal = ev;
// 如果传入的value不为null,将当前节点的value值改为传入值
if (value != null)
e.val = value;
// 如果前置指针不为null,则将当前节点拼在前置节点后面
// 这里的节点删除了。
else if (pred != null)
pred.next = e.next;
else
// 如果传入的为null,pred也为null,
// cas将头节点设置为下一个
setTabAt(tab, i, e.next);
}
break;
}
// 没有找到当前key对应的节点,继续往后遍历
pred = e;
if ((e = e.next) == null)
break;
}
}
// 如果是树节点类型,按照红黑树的规则
else if (f instanceof TreeBin) {
validated = true;
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> r, p;
// 根不为空,指定hash和key的节点存在不为空的情况下,将当前节点的值用
// pv存下来
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
V pv = p.val;
// 如果cv为null,或者传入cv值和找到的pv值相等,将pv给旧的值
if (cv == null || cv == pv ||
(pv != null && cv.equals(pv))) {
oldVal = pv;
// 如果传入的value值不为null,将value的值给到当前节点p的值
if (value != null)
p.val = value;
// 如果能够删除当前节点p返回true,则在i位置执行反树化,
else if (t.removeTreeNode(p))
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
if (validated) {
if (oldVal != null) {
if (value == null)
// 传入-1,将count树减一,返回旧值,
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
// 如果没找到旧的值,直接返回null
return null;
}
3.3、get获取元素
传入key,获取指定key对应的value值。获取元素过程通过cas机制,并未通过加锁控制,
这一点比JDK1.7的加锁实现性能上要好很多
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
// 传入key对应的hash值
int h = spread(key.hashCode());
// tab数组不为空,指定hash值对应位置的头部节点不为null
// tabAt()通过cas机制判断i位置的内存偏移量是否发生变更。来获取节点值
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
// 如果hash值和头部节点的hash值相等
if ((eh = e.hash) == h) {
// 判断key是否于当前节点的key相等,相等则返回该节点对应的value值
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// 如果hash值小于0,则说明此时链表处于转移的情况,如果是-1则当前头节点是ForwardingNode
// 那么通过forwardingNode去访问nextTable去找元素。如果是正在树化,则需要从TreeBin里
// 找这个元素,如果hash>0则是正常的链表去逐个遍历就行。
else if (eh < 0)
// find()从e开始向下遍历链表找到指定的key和hash的节点给p,如果不为null,返回节点p
// 的value值,否则返回null
return (p = e.find(h, key)) != null ? p.val : null;
// 没找到相等key的,继续向后遍历e.next,不为空的话判断 hash和key是否相等,
// 相等返回当前节点的value值,否则,继续while向后遍历
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
// 最后遍历结束,找不到就返回null
return null;
}
3.4、扩容transfer()、helpTransfer()相关方法
帮助转移数据,tab数组当前待转移的数组,f当前桶的头节点
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
// tab不为null,并且头节点是转移标识
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
int rs = resizeStamp(tab.length);
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
// CAS 将当前线程去执行数据转移,将sc的数值+1表示当前执行转移的线程数+1
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
下面继续分析transfer()方法源码部分
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
// 转移区块的数量MIN_TRANSFER_STRIDE=16 如果小于16则按照16来,不然就根据CPU数量来
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
// 如果nextTab为null,则将其初始化,长度为当前数组长度n的2倍
if (nextTab == null) { // initiating
try {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
// 那么可以将nextTab的引用传递给全局的nextTable属性
nextTable = nextTab;
// 转移索引置为n
transferIndex = n;
}
// 新数组的长度nextn
int nextn = nextTab.length;
// 构造转移标识节点fwd
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
// 当advance == true时,表明该节点已经处理过了
boolean advance = true;
boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
while (advance) {
int nextIndex, nextBound;
//迁移总进度<=0,表示所有桶都已迁移完成
if (--i >= bound || finishing)
advance = false;
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
// 下一个索引位置大于区块末尾,则nextIndex减去前一个区块,否则为0
// cas判断TRANSFERINDEX是否于nextIndex相等,是则将其按照上面规则调整
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
//确定当前线程每次分配的待迁移桶的范围为[bound, nextIndex)
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
// 扩容结束,标志为true,将nextTable属性改为null,nextTab赋值给全局table,
// sizeCtl属性2n-0.5n=1.5n 也就是2n*0.75=1.5n 也就是扩容后的容量阈值
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
// CAS判断SIZECTL是否为sc,如果是将其减少1,将状态更改为完成
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
// 如果没完成,则继续
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
// 当前线程转移完成,更改状态
finishing = advance = true;
i = n; // recheck before commit
}
}
//如果i处是ForwardingNode表示第i个桶已经有线程在负责迁移了
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
else {
// 对头部节点加锁控制,避免转移过程,有put操作冲突
synchronized (f) {
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
// hash值大于0,代表链表节点
if (fh >= 0) {
// 构造两个链表 一个是原链表 另一个是原链表的反序排列
int runBit = fh & n;
Node<K,V> lastRun = f;
for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {
ln = lastRun;
hn = null;
}
else {
hn = lastRun;
ln = null;
}
for (Node<K,V> p = f; p != lastRun; p = p.next) {
int ph = p.hash; K pk = p.key; V pv = p.val;
if ((ph & n) == 0)
ln = new Node<K,V>(ph, pk, pv, ln);
else
hn = new Node<K,V>(ph, pk, pv, hn);
}
// 在nextTable i位置处插上链表
setTabAt(nextTab, i, ln);
// 在nextTable i + n 位置处插上链表
setTabAt(nextTab, i + n, hn);
// 在table i 位置处插上ForwardingNode 表示该节点已经处理过了
setTabAt(tab, i, fwd);
// advance = true 可以执行--i动作,遍历节点
advance = true;
}
else if (f instanceof TreeBin) {
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> lo = null, loTail = null;
TreeNode<K,V> hi = null, hiTail = null;
int lc = 0, hc = 0;
for (Node<K,V> e = t.first; e != null; e = e.next) {
int h = e.hash;
TreeNode<K,V> p = new TreeNode<K,V>
(h, e.key, e.val, null, null);
if ((h & n) == 0) {
if ((p.prev = loTail) == null)
lo = p;
else
loTail.next = p;
loTail = p;
++lc;
}
else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
(hc != 0) ? new TreeBin<K,V>(lo) : t;
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
(lc != 0) ? new TreeBin<K,V>(hi) : t;
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
}
}
}
}
}
}
总结:
1、JDK1.8采用了更细粒度的锁,采用Node+Synchronized+CAS算法,来替代JDK1.7 Reentrantlock+Segment机制,降低了锁粒度,并发性能更好,并且结构上也与hashmap更加相似
2、使用Synchronized关键字比使用api级别的更加流畅,并且对关键字已经做了优化;