一.介绍

1.Segment(分段锁)

1.1 Segment

  • 容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术
  • 分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。
  • Segment 类继承于 ReentrantLock 类

  • 如图,ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作。

    第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部

    • 坏处

      这一种结构的带来的副作用是Hash的过程要比普通的HashMap要

    • 好处

      写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地分布在所有的Segment上)。

  • 注:

    • 当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放哪一个分段中,然后对分段加锁,所以当多线程put的时候,只要不是放在一个分段这种,就实现了真正的并行插入
  • 但是,在统计size的时候,可就是获取hashmap全局信息的时候,就可能需要获取所有的分段锁才能统计。

    • 其中并发级别控制了Segment的个数,在一个ConcurrentHashMap创建后Segment的个数是不能变的,扩容过程过改变的是每个Segment的大小。

1.2 ReentrantLock

  • lock拿不到锁会一直等待。tryLock是去尝试,拿不到就返回false,拿到返回true。

    tryLock是可以被打断的,被中断 的,lock是不可以。

2.数据结构

在JDK1.7版本中,ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成,如1.中图所示

3.CocurrentHashMap和HashMap异同

3.1 相同点:

  • 都实现了 Map 接口,继承了 AbstractMap 抽象类
  • jdk1.7都是数组 + 链表 ,1.8变成了数组 + 链表 + 红黑树

3.2 不同点

  • HashMap不支持并发操作,没有同步方法

4.CocurrentHashMap和HashTable的对比

  • Hashtable它把所有方法都加上synchronized关键字来实现线程安全。所有的方法都同步这样造成多个线程访问效率特别低。

  • HashTable的锁加在整个Hash表上,而ConcurrentHashMap将锁加在segment上(每个段上)

二.源码部分

1.基本属性

AbstractMap 是 Map 接口的的实现类之一,也是 HashMap, TreeMap, ConcurrentHashMap 等类的父类。

ConcurrentMap它是一个接口,是一个能够支持并发访问的java.util.map集合

Serializable :一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才能被序列化

1.1常用常量

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable {
//serialVersionUID 用来表明实现序列化类的不同版本间的兼容性
private static final long serialVersionUID = 7249069246763182397L;
/**
* The default initial capacity for this table,该表的默认初始容量
* used when not otherwise specified in a constructor.在构造函数中未指定时使用
*/
static final int DEFAULT_INITIAL_CAPACITY = 16; /**
* The default load factor for this table, used when not otherwise specified in a constructor.
* 该表的默认加载因子,在构造函数中未指定时使用。
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f; /**
* The default concurrency level for this table, used when not otherwise specified in a constructor.
* 此表的默认并发级别,在构造函数中未指定时使用。
*/
static final int DEFAULT_CONCURRENCY_LEVEL = 16; /**
* The maximum capacity, used if a higher value is implicitly specified by either of the constructors with arguments. MUST
* be a power of two <= 1<<30 to ensure that entries are indexable
* using ints.
* 最大容量
*/
static final int MAXIMUM_CAPACITY = 1 << 30; /**
* The minimum capacity for per-segment tables. Must be a power
* of two, at least two to avoid immediate resizing on next use
* after lazy construction.
* 每个段表的最小容量。必须是2的幂,至少为2,以避免在延迟构造后再次使用时立即调整大小。
*/
static final int MIN_SEGMENT_TABLE_CAPACITY = 2; /**
* The maximum number of segments to allow; used to bound
* constructor arguments. Must be power of two less than 1 << 24.
* 允许的最大段数;用于绑定构造函数参数。
*/
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative /**
* Number of unsynchronized retries in size and containsValue
* methods before resorting to locking. This is used to avoid
* unbounded retries if tables undergo continuous modification
* which would make it impossible to obtain an accurate result.
* 在使用锁定之前,在size和containsValue方法上的未同步重试次数。如果表经历了连续的修改,从而无法获得准确的结果,这可以用来避免无边界重试。
* 在size方法和containsValue方法,会优先采用乐观的方式不加锁,直到重试次数达到2,才会对所有Segment加锁
* 这个值的设定,是为了避免无限次的重试。后边size方法会详讲怎么实现乐观机制的。
*/
static final int RETRIES_BEFORE_LOCK = 2;
/**
* Mask value for indexing into segments. The upper bits of a key's hash code are used to choose the segment.
* 用于索引段的掩码值,用于根据元素的hash值定位所在的 Segment 下标
*/
final int segmentMask; /**
* Shift value for indexing within segments.
* 在段内索引的移位值
*/
final int segmentShift; /**
* The segments, each of which is a specialized hash table.
* Segment 组成的数组,每一个 Segment 都可以看做是一个特殊的 HashMap
*/
final Segment<K,V>[] segments;

1.2内部类

/**
* ConcurrentHashMap list entry. Note that this is never exported out as a user-visible Map.Entry.
* ConcurrentHashMap列表条目。注意,这永远不会导出为用户可见的Map.Entry。
* HashEntry,存在于每个Segment中,它就类似于HashMap中的Node,用于存储键值对的具体数据和维护单向链表的关系
*/
static final class HashEntry<K,V> {
final int hash;
final K key;
//value和next都用 volatile 修饰,用于保证内存可见性和禁止指令重排序
volatile V value;
volatile HashEntry<K,V> next; HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
static final class Segment<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L; /**
* The maximum number of times to tryLock in a prescan before possibly blocking on acquire in preparation for a locked
* segment operation. On multiprocessors, using a bounded
* number of retries maintains cache acquired while locating
* nodes.
* 在为锁定段操作做准备而可能阻塞之前,在预扫描中尝试lock的最大次数。在多处理器上,使用有限的重试次数来维护在定位节点时获取的缓存。
*/
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1; /**
* The per-segment table. Elements are accessed via
* entryAt/setEntryAt providing volatile semantics.
* 每个segment中的键值对数组
*/
transient volatile HashEntry<K,V>[] table; /**
* The number of elements. Accessed only either within locks
* or among other volatile reads that maintain visibility.
* Segment中的元素个数
*/
transient int count; /**
* The total number of mutative operations in this segment.
* Even though this may overflows 32 bits, it provides
* sufficient accuracy for stability checks in CHM isEmpty()
* and size() methods. Accessed only either within locks or
* among other volatile reads that maintain visibility.
* 每次 table 结构修改时,modCount增加1
*/
transient int modCount; /**
* The table is rehashed when its size exceeds this threshold.
* 当表的大小超过这个阈值时,表将被重新散列。
* (The value of this field is always <tt>(int)(capacity *
* loadFactor)</tt>.)
* segment扩容的阈值
*/
transient int threshold; /**
* The load factor for the hash table. Even though this value
* is same for all segments, it is replicated to avoid needing
* links to outer object.
* @serial
* 加载因子
*/
final float loadFactor;
//构造函数
Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
this.loadFactor = lf;
this.threshold = threshold;
this.table = tab;
}

2.构造函数

/**
* Creates a new, empty map with a default initial capacity (16),
* load factor (0.75) and concurrencyLevel (16).
* 创建一个新的空映射,具有默认的初始容量(16),负载因子(0.75)和并发级别(16)。
*/
public ConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
/**
* Creates a new, empty map with the specified initial capacity,
* and with default load factor (0.75) and concurrencyLevel (16).
* 使用指定的初始容量创建一个新的空映射,以及默认的负载因子(0.75)和并发级别(16)。
* @param initialCapacity the initial capacity. The implementation
* performs internal sizing to accommodate this many elements.
* @throws IllegalArgumentException if the initial capacity of
* elements is negative.
*/
public ConcurrentHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
/**
* Creates a new, empty map with the specified initial capacity
* and load factor and with the default concurrencyLevel (16).
* 使用指定的初始容量,负载因子和默认的concurrencyLevel (16)创建一个新的空映射
* @param initialCapacity The implementation performs internal
* sizing to accommodate this many elements.
* @param loadFactor the load factor threshold, used to control resizing.
* Resizing may be performed when the average number of elements per
* bin exceeds this threshold.
* @throws IllegalArgumentException if the initial capacity of
* elements is negative or the load factor is nonpositive
*
* @since 1.6
*/
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL);
}
/**
* Creates a new map with the same mappings as the given map.
* 使用与给定映射相同的映射创建一个新映射。
* The map is created with a capacity of 1.5 times the number
* of mappings in the given map or 16 (whichever is greater),
* and a default load factor (0.75) and concurrencyLevel (16).
* 容量为原map * 1.5倍 和 16 中大的那个,加载因子为0.75,concurrencyLevel为16
* @param m the map
*/
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
//构建新的table
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY),
DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
//将原映射put进去
putAll(m);
}
/**
* Creates a new, empty map with the specified initial capacity, load factor and concurrency level.
* 使用指定的初始容量、负载因子和并发级别创建一个新的空映射。
* 所有的构造函数最终都会调用这个构造函数
* @param initialCapacity the initial capacity. The implementation
* performs internal sizing to accommodate this many elements.
* @param loadFactor the load factor threshold, used to control resizing.
* Resizing may be performed when the average number of elements per
* bin exceeds this threshold.
* @param concurrencyLevel the estimated number of concurrently
* updating threads. The implementation performs internal sizing
* to try to accommodate this many threads.
* @throws IllegalArgumentException if the initial capacity is
* negative or the load factor or concurrencyLevel are
* nonpositive.
*/
@SuppressWarnings("unchecked")
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
//如果加载因子<=0,初始容量为负,并发级别<=0,则抛出异常
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
//并发级别不能大于16
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments 找到2次幂大小的最佳匹配参数
//偏移量
//默认concurrencyLevel = 16, 所以ssize在默认情况下也是16,此时 sshift = 4
int sshift = 0;
//segmen的大小
int ssize = 1;
//找到>concurrencyLevel的最小2次幂
//sshift相当于ssize从1向左移的次数
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
//段偏移量,默认值28
this.segmentShift = 32 - sshift;
//掩码
this.segmentMask = ssize - 1;
//对初始容量再进行判断
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//计算一个segment中数组的数量
int c = initialCapacity / ssize;
//向上取整
if (c * ssize < initialCapacity)
++c;
//最小分段为2
int cap = MIN_SEGMENT_TABLE_CAPACITY;
//同样地,将segment容量取到大于实际需要的最小2次幂
while (cap < c)
cap <<= 1;
// create segments and segments[0]
//创建segment数组,并初始化segmen[0]
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
//创建ssize大小的数组
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
//将obj对象的偏移量为offset的位置修改为value,因为Java中没有内存操作,而Unsafe的这个操作正好补充了内存操作的不足。也可以用于数组操作,比如ConcurrentHashMap中就大量用到了该操作
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}

3. put()

/**
* Maps the specified key to the specified value in this table.
* 将指定的键映射到该表中的指定值。
* Neither the key nor the value can be null.
* 键和值都不能为空。
* <p> The value can be retrieved by calling the <tt>get</tt> method
* with a key that is equal to the original key.
* 可以通过调用get方法检索该值,该方法具有与原始键相等的键
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>
* @throws NullPointerException if the specified key or value is null
*/
//告诉编译器忽略警告。不用在编译完成后出现警告
@SuppressWarnings("unchecked")
public V put(K key, V value) {
Segment<K,V> s;
//如果指定的值为空,抛出异常
if (value == null)
throw new NullPointerException();
int hash = hash(key);
//一个键值对在Segment数组中下标
int j = (hash >>> segmentShift) & segmentMask;
//这里是用Unsafe类的原子操作找到Segment数组中j下标的 Segment 对象
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
//返回segment类型,如果不存在则初始化
s = ensureSegment(j);
//将键值对通过segment中put方法put,返回值为:
return s.put(key, hash, value, false);
}
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
//这里通过tryLock尝试加锁,如果加锁成功,返回null,否则执行 scanAndLockForPut方法
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
//保存旧value
V oldValue;
try {
HashEntry<K,V>[] tab = table;
//二次哈希计算,求hashentry数组下标
int index = (tab.length - 1) & hash;
//找到下标的头结点
HashEntry<K,V> first = entryAt(tab, index);
//遍历操作
for (HashEntry<K,V> e = first;;) {
//当首结点不为空的时候
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
//当首结点为空,或者遍历晚时,以下
//node值不为空时,说明调用scanAndLockForPut()方法时,遍历没有找到该节点,创建了新结点给node,“预热”
if (node != null)
//直接头插法
node.setNext(first);
else
//新建结点,头插法
node = new HashEntry<K,V>(hash, key, value, first);
count加1
int c = count + 1;
//当c大于阈值且table长度没达到最大值的时候扩容
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
//否则,将结点插到数组下标为index的位置
setEntryAt(tab, index, node);
//增加修改次数
++modCount;
//count也++
count = c;
//因为没有旧的value所以设置为null
oldValue = null;
break;
}
}
} finally {
unlock();
}
//返回oldvalue
return oldValue;
}

4.get()

/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
* 返回指定键映射到的值,或是null
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code key.equals(k)},
* then this method returns {@code v}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
*
* @throws NullPointerException if the specified key is null
*/
public V get(Object key) {
Segment<K,V> s; // manually integrate access methods to reduce overhead 手动集成访问方法以减少开销
HashEntry<K,V>[] tab;
//计算hash值
int h = hash(key);
//从主存中取出最新的结点
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
//如果若Segment不为空,且链表也不为空,则遍历查找节点
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
e != null; e = e.next) {
K k;
//找到结点,返回value
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
//无,返回空
return null;
}

5. ensureSegment()

/**
* Returns the segment for the given index, creating it and recording in segment table (via CAS) if not already present.
* 返回给定索引的段,创建它并(通过CAS)在段表中记录(如果不存在)。
* @param k the index
* @return the segment
*/
@SuppressWarnings("unchecked")
////k为 (hash >>> segmentShift) & segmentMask 计算出的segment下标
private Segment<K,V> ensureSegment(int k) {
final Segment<K,V>[] ss = this.segments;
////u代表 k 的偏移量,用于通过 UNSAFE 获取主内存最新的实际 K 值
long u = (k << SSHIFT) + SBASE; // raw offset
Segment<K,V> seg;
//从内存中取到最新的下标位置的 Segment 对象,判断是否为空
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
//如果为空,则按照ss[0]为原型来建造segment
Segment<K,V> proto = ss[0]; // use segment 0 as prototype
//容量为ss[0]的长度
int cap = proto.table.length;
//加载因子也为ss[0]的
float lf = proto.loadFactor;
//算出阈值
int threshold = (int)(cap * lf);
//再创建Segment 对应的 HashEntry 数组
HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) { // recheck 再次从内存中取到最新的下标位置的 Segment 对象,判断是否为空
//创建segment对象
Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
//循环检查 u下标位置的 Segment 是否为空
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
//不为空,说明有其他线程已经创建对象,则用seg保存
//若为空,则当前下标的Segment对象为空,就把它替换为最新创建出来的 s 对象
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
break;
}
}
}
//返回segment
return seg;
}

6.scanAndLockForPut()

/**
* Scans for a node containing given key while trying to
* acquire lock, creating and returning one if not found. Upon
* return, guarantees that lock is held. UNlike in most
* methods, calls to method equals are not screened: Since
* traversal speed doesn't matter, we might as well help warm
* up the associated code and accesses as well.
* put()方法第一步抢锁失败之后,就会执行此方法
* @return a new node if key not found, else null
*/
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
//找到HashEntry数组的下标的首结点
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
//初始化重试次数,为-1
int retries = -1; // negative while locating node
//一直尝试抢锁
while (!tryLock()) {
HashEntry<K,V> f; // to recheck first below
//
if (retries < 0) {
//首结点为空,预先创建一个新的结点,在hashentry数组上,retries++
if (e == null) {
if (node == null) // speculatively create node
node = new HashEntry<K,V>(hash, key, value, null);
retries = 0;
}
//如果first有值,并且相对应,则也把retries = 0
else if (key.equals(e.key))
retries = 0;
else
//不对应的话,就从判断语句开始
//同样的如果空,就新建结点,否则找到该结点,最后retries = 0
e = e.next;
}
else if (++retries > MAX_SCAN_RETRIES) {
//lock拿不到锁会一直等待。tryLock是去尝试,拿不到就返回false,拿到返回true。
lock();
break;
}
//retries为偶数的时候&1为0,检查在这段时间内first结点是否有改变
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
e = first = f; // re-traverse if entry changed如果条目改变了,重新遍历
retries = -1;
}
}
return node;
}
  • scanAndLockForPut 这个方法可以确保返回时,当前线程一定是获取到锁的状态。

7.rehash()

  • 当 put 方法时,发现元素个数超过了阈值,则会扩容

  • 但是segment互相之间并不影响

/**
* Doubles size of table and repacks entries, also adding the given node to new table
* 将表的大小增加一倍并重新打包条目,还将给定节点添加到新表中
*/
@SuppressWarnings("unchecked")
private void rehash(HashEntry<K,V> node) {
/*
* Reclassify nodes in each list to new table. Because we
* are using power-of-two expansion, the elements from
* each bin must either stay at same index, or move with a
* power of two offset. We eliminate unnecessary node
* creation by catching cases where old nodes can be
* reused because their next fields won't change.
* Statistically, at the default threshold, only about
* one-sixth of them need cloning when a table
* doubles. The nodes they replace will be garbage
* collectable as soon as they are no longer referenced by
* any reader thread that may be in the midst of
* concurrently traversing table. Entry accesses use plain
* array indexing because they are followed by volatile
* table write.
*/
HashEntry<K,V>[] oldTable = table;
//oldCapacity为原表的长度
int oldCapacity = oldTable.length;
//新容量为原来的2倍
int newCapacity = oldCapacity << 1;
//再计算新的阈值
threshold = (int)(newCapacity * loadFactor);
//创建新容量的hashentry
HashEntry<K,V>[] newTable =
(HashEntry<K,V>[]) new HashEntry[newCapacity];
//哈希表大小掩码 用于计算索引值
int sizeMask = newCapacity - 1;
//遍历原表
for (int i = 0; i < oldCapacity ; i++) {
//// e 为链表的第一个结点
HashEntry<K,V> e = oldTable[i];
//如果首结点不为空
if (e != null) {
//保存e的next结点
HashEntry<K,V> next = e.next;
//重新计算e的index
int idx = e.hash & sizeMask;
//如果next为null,说明此位置没发生哈希冲突,直接将e插入
if (next == null) // Single node on list
newTable[idx] = e;
else { // Reuse consecutive sequence at same slot 重复使用同一槽位的连续序列
HashEntry<K,V> lastRun = e;
int lastIdx = idx;
//遍历列表
for (HashEntry<K,V> last = next;
last != null;
last = last.next) {
//计算当前遍历到的节点的新下标
int k = last.hash & sizeMask;
//若 k 不等于 lastIdx,则把last更新
if (k != lastIdx) {
lastIdx = k;
lastRun = last;
}
}
//新表的lastidx位置放入和lastrun index相同的结点
newTable[lastIdx] = lastRun;
// Clone remaining nodes 克隆剩余节点
for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
//通过遍历建立新结点的方式
V v = p.value;
int h = p.hash;
int k = h & sizeMask;
HashEntry<K,V> n = newTable[k];
newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
}
}
}
}
//添加新节点,put方法传入的结点
int nodeIndex = node.hash & sizeMask; // add the new node
node.setNext(newTable[nodeIndex]);
newTable[nodeIndex] = node;
table = newTable;
}

8.remove()

/**
* Remove; match on key only if value null, else match both.
*/
final V remove(Object key, int hash, Object value) {
//抢锁
if (!tryLock())
scanAndLock(key, hash);
V oldValue = null;
try {
HashEntry<K,V>[] tab = table;
//找到哈希表对应下标的头结点
int index = (tab.length - 1) & hash;
HashEntry<K,V> e = entryAt(tab, index);
HashEntry<K,V> pred = null;
//如果首结点不为null
while (e != null) {
K k;
//记录next
HashEntry<K,V> next = e.next;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
V v = e.value;
if (value == null || value == v || value.equals(v)) {
if (pred == null)
/**
* static final <K,V> void setEntryAt(HashEntry<K,V>[] tab, int i,
* HashEntry<K,V> e) {
* UNSAFE.putOrderedObject(tab, ((long)i << TSHIFT) + TBASE, e);
* putOrderedObject: 将这个方法名拆成 put ordered Object
*/
setEntryAt(tab, index, next);
else
pred.setNext(next);
++modCount;
--count;
oldValue = v;
}
break;
//用的Unsafe的方法直接替换数组对应的值(此时的数组对应的空,所以可以直接插入),然后就是解锁,返回旧的值了。
}
pred = e;
e = next;
}
} finally {
unlock();
}
return oldValue;
}

9.size()

/**
* Returns the number of key-value mappings in this map.
* 返回此映射中的键-值映射的数量。
* If the map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>.
*
* @return the number of key-value mappings in this map
*/
public int size() {
// Try a few times to get accurate count. On failure due to
// continuous async changes in table, resort to locking.
//试几次,得到准确的数字。如果由于表中的连续异步更改而导致失败,则使用锁定。
final Segment<K,V>[] segments = this.segments;
int size;
boolean overflow; // true if size overflows 32 bits
long sum; // sum of modCounts 的和
long last = 0L; // previous sum
int retries = -1; // first iteration isn't retry 重试次数
try {
for (;;) {
//如果超过重试次数,则不再重试,而是把所有Segment都加锁,再统计 size
if (retries++ == RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
ensureSegment(j).lock(); // force creation
}
sum = 0L;
size = 0;
overflow = false;
//遍历所有Segment
//先不都锁上,每个段统计count,并记录modcount
//最后如果modcount不相等,则重新循环,直到超出最大重试次数
//则强制锁上所有segment,然后统计次数返回
for (int j = 0; j < segments.length; ++j) {
Segment<K,V> seg = segmentAt(segments, j);
if (seg != null) {
sum += seg.modCount;
int c = seg.count;
if (c < 0 || (size += c) < 0)
overflow = true;
}
}
if (sum == last)
break;
last = sum;
}
} finally {
if (retries > RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
segmentAt(segments, j).unlock();
}
}
return overflow ? Integer.MAX_VALUE : size;
}

三.总结

1. ConcurrentHashMap中变量使用final和volatile修饰有什么用呢?

  • final :HashEntry里面除了value值不是final修饰的,其他都被final修饰了,所以在HashEntry链表里面添加HashEntry的时候,只能添加到头节点,不能添加到尾节点,因为HashEntry里面的next值是被final修饰的,不能修改。

  • volatile:来保证某个变量内存的改变对其他线程即时可见,在配合CAS可以实现不加锁对并发操作的支持。

    如:get操作可以无锁是由于Node的元素val和指针next是用volatile修饰的,在多线程环境下线程A修改结点的val或者新增节点的时候是对线程B可见的

2. 什么是哈希算法?

  • 是一种将任意内容的输入转换成相同长度输出的加密方式,其输出被称为哈希值。

3. 为什么用两次hash?

  • 构造分离锁,操作的时候不会锁住整个表,提高并发能力

4. hashmap在多线程下的隐患是什么?可以用用什么代替

  • jdk1.7版本存在put操作时存在丢失数据的情况

    jdk1.8版本虽然解决了死循环问题,但是也有数据覆盖问题

  • 可用ConcurrentHashMap代替HashMap

5. 并发问题分析

ConcurrentHashMap的get操作时候,新增,修改,删除都是要考虑并发问题的

。。。

6. segmentShift、segmentMask、sshift、ssize和SBASE关系

  • 一个键值对在Segment数组中下标为:

    (hash >>> segmentShift) & segmentMask

  • 其中,

    • segmentShift = 32 - sshift
    • segmentMask = ssize - 1
    • 其中,
      • 2^sshif=ssize
      • ssize为concurrencyLevel的最小2次幂

ConcurrentHashMap (jdk1.7)源码学习的更多相关文章

  1. JDK1.8源码学习-String

    JDK1.8源码学习-String 目录 一.String简介 String类是Java中最常用的类之一,所有字符串的字面量都是String类的实例,字符串是常量,在定义之后不能被改变. 二.定义 p ...

  2. JDK1.8源码学习-Object

    JDK1.8源码学习-Object 目录 一.方法简介 1.一个本地方法,主要作用是将本地方法注册到虚拟机中. private static native void registerNatives() ...

  3. JDK1.8源码学习-LinkedList

    JDK1.8源码学习-LinkedList 目录 一.LinkedList简介 LinkedList是一个继承于AbstractSequentialList的双向链表,是可以在任意位置进行插入和移除操 ...

  4. JDK1.8源码学习-ArrayList

    JDK1.8源码学习-ArrayList 目录 一.ArrayList简介 为了弥补普通数组无法自动扩容的不足,Java提供了集合类,其中ArrayList对数组进行了封装,使其可以自动的扩容或缩小长 ...

  5. JDK1.8源码学习-HashMap

    JDK1.8源码学习-HashMap 目录 一.HashMap简介 HashMap 主要用来存放键值对,它是基于哈希表的Map接口实现的,是常用的Java集合之一. 我们都知道在JDK1.8 之前 的 ...

  6. jdk1.8源码学习笔记

    前言: 前一段时间开始学习了一些基本的数据结构和算法,算是弥补了这方面的知识短板,但是仅仅是对一些算法的了解,目前工作当中也并没有应用到这些,因此希望通过结合实际例子来学习,巩固之前学到的内容,思前想 ...

  7. Java JDK1.8源码学习之路 1 Object

    写在最前 对于一个合格的后端程序员来说,现行的流行框架早已经能胜任基本的企业开发,Springboot 任何的框架都把重复的工作更佳简单/优化的解决掉,但是完全陷入在这样的温水里面, 好比温水煮青蛙, ...

  8. Java JDK1.8源码学习之路 2 String

    写在最前 String 作为我们最常使用的一个Java类,注意,它是一个引用类型,不是基本类型,并且是一个不可变对象,一旦定义 不再改变 经常会定义一段代码: String temp = " ...

  9. JDK1.8源码学习-String-hashCode方法为什么选择数字31作为乘子

    1. 背景 某天,我在写代码的时候,无意中点开了 String hashCode 方法.然后大致看了一下 hashCode 的实现,发现并不是很复杂.但是我从源码中发现了一个奇怪的数字,也就是本文的主 ...

  10. Java并发包源码学习系列:JDK1.8的ConcurrentHashMap源码解析

    目录 为什么要使用ConcurrentHashMap? ConcurrentHashMap的结构特点 Java8之前 Java8之后 基本常量 重要成员变量 构造方法 tableSizeFor put ...

随机推荐

  1. Python入门(上)

    Python入门(上) Python入门(上) 简介 Python 基础语法 行与缩进 注释 运算符 标准数据类型 变量 编程流程 顺序(略) 分支 if 循环 for while break 和 c ...

  2. 记一次 .NET 某消防物联网 后台服务 内存泄漏分析

    一:背景 1. 讲故事 去年十月份有位朋友从微信找到我,说他的程序内存要炸掉了...截图如下: 时间有点久,图片都被清理了,不过有点讽刺的是,自己的程序本身就是做监控的,结果自己出了问题,太尴尬了 二 ...

  3. 数据库锁(mysql)

    InnoDB支持表.行(默认)级锁,而MyISAM支持表级锁 本文着中介绍InnoDB对应的锁. mysql锁主要分为以下三类: 表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发生锁冲突的概率最高 ...

  4. 如何使用 GitHub Pages 维护自己的博客

    目录 前置知识 实际操作 声明 本文地址:如何使用 GitHub Pages 维护自己的博客 前置知识 首先,你应该知道如何用 Hexo 在本地搭建一个博客系统,具体见 Hexo. 其次,我们如果想使 ...

  5. Linux防止文件被误删除或修改

    chattr简介 Linux没有回收站,一旦文件或文件夹被误删除,要寻找回来很麻烦,不如事先对一些重要的文件做一些保护,这时我们需要一个命令chattr,其使用格式为 chattr 操作符 属性 文件 ...

  6. Solon Web 开发,十一、国际化

    Solon Web 开发 一.开始 二.开发知识准备 三.打包与运行 四.请求上下文 五.数据访问.事务与缓存应用 六.过滤器.处理.拦截器 七.视图模板与Mvc注解 八.校验.及定制与扩展 九.跨域 ...

  7. Manacher算法求解回文字符串

    Manacher算法可以在\(O(N)\)时间内求解出一个字符串的所有回文子串(正反遍历相同的字串). 注:回文串显然有两种,一种是奇数长度,如abczcba,有一个中心字符z:另外一种是偶数个长度, ...

  8. gin中从reader读取数据数据

    package main import ( "fmt" "github.com/gin-gonic/gin" "net/http" &quo ...

  9. zabbix报错整理

    1.cannot connect to [[172.16.2.225]:10050]: [113] No route to host 这种一般是网络连接问题 排查:在server上telnet 172 ...

  10. web下载文件的头消息

    resp.setHeader("Content-disposition","attachment;filename="+filename);