文章目录
前言
作为一个程序员,不管是为了提升自己还是找工作,阅读源码都是必经之路。笔者是一个工作近3年,毕业近2年的程序员,在最近的工作中越来越觉得自己的技术遇到了瓶颈期,通过平时的工作已经很难有所提升了,遂开始学习源码,欢迎博友一起交流,博主如有写错的地方欢迎留言指正。
一.基础知识
1.1.数据结构
数组:没啥好讲的,常用的线性表数据结构之一,特点是查询快,长度一旦指定无法更改。
链表:链表通常有三种,即单向链表,双向链表,循环链表(包括单向,双向)。用JAVA代码表示大致如下:
public class Node{//单向链表
Node nextNode;
Object data;
public Node(Object data,Node nextNode){
this.data=data;
this.nextNode=nextNode;
}
}
public class Node{//双向链表
Node prevNode;
Node nextNode;
Object data;
public Node(Object data,Node prevNode,Node nextNode){
this.data=data;
this.prevNode=prevNode;
this.nextNode=nextNode;
}
}
/* 双节点单向循环链表 */
Node node1=new Node(null,null);
Node node2=new Node(null,node1);
node1.nextNode=node2;
/* 三节点双向循环链表 */
Node node1=new Node(null,null,null);
Node node2=new Node(null,node1,null);
Node node3=new Node(null,node2,node1);
node1.prevNode=node3;
node1.nextNode=node2;
node2.nextNode=node3;
由于链表没有索引,单向链表通常使用头插法进行插入操作。
1.2.运算符
按位与运算符:&,都为1则为1,否则为0
按位或运算符:|,都为0则为0,否则为1
按位异或运算符:^,相同为0,异同为1
int i1=10;// 1010
int i2=6;// 0110
System.out.println(i1&i2);//0010 = 2
System.out.println(i1|i2);//1110 = 14
System.out.println(i1^i2);//1100 = 12
原码:数字二进制表示
反码:正数的反码即为其原码,负数的反码为其除符号位外的位数取反
补码:正数的补码还是原码,负数的补码为其反码加1。负数以补码方式存储与内存中,正数以原码方式存储与内存中。
(虽然不太想写,但是如果不知道反码和补码,那就没法理解负数的运算符操作了)。
int i1=10;
// 原,反,补码:0。。26个0。。01010
int i2=-6;
// 原码:1。。26个0。。00110
// 反码:1。。26个1。。11001
// 补码:1。。26个1。。11010
//两个数字使用运算符比较时将比较其在内存中存储的二进制数,即负数使用补码进行运算符操作
System.out.println(i1&i2);//0。。26个0。。01010 = 10
System.out.println(i1|i2);//1。。26个1。。11010 = -6
System.out.println(i1^i2);//1。。26个0。。10000 = -16
左移运算符:<<,num << i,相当于num乘以2的i次方
右移运算符:>>,num >> i,相当于num除以2的i次方
无符号右移:>>>,忽略符号位,空位都以0补齐
1.3.sun.misc.Unsafe类常用方法
这个类笔者也不是很熟悉,大部分方法都是涉及到JVM底层原理,被广泛在源码中使用。
1.3.1初始化
ConcurrentHashMap使用getUnsafe()初始化Unsafe 对象使用的方法,但是我们自己使用时会抛SecurityException,因为我们用的类加载器不是BootstrapClassLoader 即核心类加载器,而是用的AppClassLoader系统加载器。
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
虽然不能使用getUnsafe方法获取,但是Unsafe提供了我们使用反射获取对象的后门,获取方式如下:
public class UnsafeCAS {
public String[] arrs={"s1","s2","s3","s4"};
public String str="before";
public static sun.misc.Unsafe unsafe=null;
static{
//初始化Unsafe
try {
Field f=sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe=(sun.misc.Unsafe)f.get(null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.3.2CAS操作
下面到了精髓的地方,Unsafe的compareAndSwapObject方法(经典的CAS),这个方法有四个参数,第一个参数传入对象,第二个参数传入要修改属性的偏移量,通过objectFieldOffset方法获取,第三个参数传对象要修改的属性,第四个参数传对象属性修改后的值。这个方法会比较对象属性在内存中的值,一致返回true并修改成功,不一致返回false,作用在于保证多个线程同时对一个对象进行操作时保证要修改的值线程安全。
//public String str="before";
public static void basicCAS() throws NoSuchFieldException, SecurityException{
UnsafeCAS uc=new UnsafeCAS();
//获取UnsafeCAS类的属性str传递给内存时的偏移量,类的任何属性给内存的偏移量都不会改变且是唯一的
long offSet=unsafe.objectFieldOffset(UnsafeCAS.class.getDeclaredField("str"));
//输出true,返回t在内存中的属性s是否被修改
System.out.println(unsafe.compareAndSwapObject(uc, offSet, uc.str, "after"));
//输出after
System.out.println(uc.str);
}
在对数组执行CAS操作时使用如下方法
//public String[] arrs={"s1","s2","s3","s4"};
public static void arrayCAS() throws NoSuchFieldException, SecurityException{
UnsafeCAS uc=new UnsafeCAS();
//String[]类的基础偏移量
int baseOffset=unsafe.arrayBaseOffset(String[].class);
//String[]类在内存地址的比例因子
int indexScale=unsafe.arrayIndexScale(String[].class);
//Integer.numberOfLeadingZeros:返回int型数字转化为二进制从符号位到首位1之间的0的个数
int arrayShift=31 - Integer.numberOfLeadingZeros(indexScale);
//对arrs[0]进行CAS修改
unsafe.putOrderedObject(uc.arrs, baseOffset, "s11");
//对arrs[1]进行CAS修改
unsafe.putOrderedObject(uc.arrs, baseOffset+indexScale, "s22");
//对arrs[2]进行CAS修改
unsafe.putOrderedObject(uc.arrs, baseOffset+2*indexScale, "s33");
//三种获取数组元素的方式
//获取arrs[0],输出s11
System.out.println(uc.arrs[0]);
//获取arrs[1],输出s22
System.out.println(unsafe.getObject(uc.arrs, baseOffset+indexScale));
//获取arrs[2],输出s33
System.out.println(unsafe.getObjectVolatile(uc.arrs, (2<<arrayShift)+baseOffset));
}
二.HashMap
2.1.HashMap大致结构
简单的来说,我们可以把HashMap理解为一个Entry<K,V>数组,而数组中的每个Entry对象都是一个单链表。在调用HashMap构造方法时,会为此数组初始化大小。在调用put方法时,通过对K.hashCode()一系列运算符操作计算index,如果table[index]已存在Entry对象则使用头插法插在链表的头上。
2.2.HashMap属性解析(仅介绍与本文相关的属性)
DEFAULT_INITIAL_CAPACITY :table的默认容量,默认16。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
MAXIMUM_CAPACITY :table的最大容量。
static final int MAXIMUM_CAPACITY = 1 << 30;
DEFAULT_LOAD_FACTOR :默认加载因子,与扩容有关。
static final float DEFAULT_LOAD_FACTOR = 0.75f;
EMPTY_TABLE :一个空的table实例,用于创建新的table对象。
static final Entry<?,?>[] EMPTY_TABLE = {};
table :存放Entry<K,V>链表对象的数组。
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
size:table中Entry<K,V>节点个数,即HashMap中元素个数,每执行put方法时++。
transient int size;
threshold:阈值,用于计算扩容。
int threshold;
loadFactor:实际加载因子,不指定则为DEFAULT_LOAD_FACTOR 。
final float loadFactor;
modCount:HashMap实际修改次数,执行put,remove等操作时++。
transient int modCount;
ALTERNATIVE_HASHING_THRESHOLD_DEFAULT:
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
hashSeed :hash种子,为了使hash()方法计算出来的hash更复杂。
transient int hashSeed = 0;
2.3.HashMap 无参构造方法解析
默认调用有参构造方法
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//使用无参构造调用此构造方法时,initialCapacity=16,loadFactor=0.75f
public HashMap(int initialCapacity, float loadFactor) {
//初始容量为0时抛出异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//初始容量大于最大容量则等于最大容量
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//加载因子小于等于0或不是浮点数时抛出异常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
//给加载因子及阈值赋值
this.loadFactor = loadFactor;
threshold = initialCapacity;
//此方法在HashMap中为空,用于HashMap的子类扩展
init();
}
2.4.HashMap put方法解析
2.4.1put()
public V put(K key, V value) {
//判断table是否已经初始化
if (table == EMPTY_TABLE) {
//通过阈值初始化table,解析见2.4.2
inflateTable(threshold);
}
if (key == null)
//单独处理key=null的情况,将Entry放在table[0]的位置
//相比于其他的key少了计算hash和数组下标的过程
return putForNullKey(value);
//通过key的hashcode与一系列hashcode右移后的值进行异或运算
//目的在于使hashcode的二进制值得每一位都参与到数组下标的计算中去
int hash = hash(key);
//通过hash值计算数组下标
int i = indexFor(hash, table.length);
//遍历table[i]位置的Entry链表判断是否已有相同key的Entry节点
//如果有则替换成新的value并返回旧的value
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
//此方法方法体为空,用于子类扩展
e.recordAccess(this);
return oldValue;
}
}
//修改次数+1
modCount++;
//判断是否需要扩容,使用头插法插入Entry节点,源码见2.4.3
addEntry(hash, key, value, i);
return null;
}
2.4.2inflateTable()通过阈值初始化table
private void inflateTable(int toSize) {
//获取一个大于等于toSize的2的幂次方的数
int capacity = roundUpToPowerOf2(toSize);
//使阈值等于capacity乘加载因子
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
//初始化hash种子,基本为false,见2.4.7
initHashSeedAsNeeded(capacity);
}
2.4.3addEntry()判断是否需要扩容,使用头插法插入Entry节点
void addEntry(int hash, K key, V value, int bucketIndex) {
//如果HashMap当前大小大于阈值且table[bucketIndex]位置没有Entry,则进行扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
//重新分配Entry节点到扩容后的数组,源码见2.4.4
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
//扩容后Entry存放于新table的位置
bucketIndex = indexFor(hash, table.length);
}
//头插法插入Entry节点,源码见2.4.5
createEntry(hash, key, value, bucketIndex);
}
2.4.4resize()重新分配Entry节点到扩容后的数组
void resize(int newCapacity) {
//新建一个2倍容量的Entry数组
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
//迁移Entry,源码见2.4.6
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
2.4.5createEntry()使用头插法插入Entry节点并将新的头指向table[bucketIndex]
void createEntry(int hash, K key, V value, int bucketIndex) {
//bucketIndex位置的值赋给e
Entry<K,V> e = table[bucketIndex];
//使用头插法将新Entry的next节点指向e并将引用指向table[bucketIndex]
//这里的目的主要在于使用头插后将链表的头节点指向table[bucketIndex]
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
2.4.6transfer()将Entry迁移到新的table
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
//遍历table数组上每一个非空的位置
for (Entry<K,V> e : table) {
while(null != e) {
//将非空位置的Entry链表转移到新的数组
Entry<K,V> next = e.next;
if (rehash) {
//重新计算hash,和put()里面套路一样
e.hash = null == e.key ? 0 : hash(e.key);
}
//计算存放于新数组的位置,计算方法为hash & (newCapacity-1)
//这里的规律在于,假设Entry在老数组上的位置为i,则在新数组上的位置不是i就是2i
//而且,由于使用头插法的原因,老数组上的链表转移到新数组上时将收尾导致
//在多线程同时对HashMap进行扩容是容易产生循环链表,从而在遍历时变成死循环
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
2.4.7initHashSeedAsNeeded()如果有需要初始化hashseed
JDK1.7定义了一个新的属性jdk.map.althashing.threshold(默认值-1),只要该属性不被改动,则hashSeed一定为0。要计算新的hashSeed可以使用-Djdk.map.althashing.threshold=0修改threshold的值。
final boolean initHashSeedAsNeeded(int capacity) {
//默认为false
boolean currentAltHashing = hashSeed != 0;
//sun.misc.VM.isBooted()默认返回false
//这里大部分是否也为false
boolean useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
//false^false=false
boolean switching = currentAltHashing ^ useAltHashing;
//switching大部分时候为false,因为大部分时候不需要重设hashseed,从而使得hash算法更复杂
if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
return switching;
}
2.4.8hash(),hashcode计算hash
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
2.5.HashMap get方法解析
2.5.1get()
public V get(Object key) {
if (key == null)
//处理key=null的情况,直接去table[0]的位置找
return getForNullKey();
//见5.2
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
2.5.2getEntry()
final Entry<K,V> getEntry(Object key) {
//如果size为0返回空
if (size == 0) {
return null;
}
//计算hash
int hash = (key == null) ? 0 : hash(key);
//计算数组下标后遍历位于该位置的Entry链表
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 != null && key.equals(k))))
return e;
}
return null;
}
三.ConcurrentHashMap
3.1ConcurrentHashMap大致结构
ConcurrentHashMap的最外层是一个Segment数组,每个Segment里面还有一个小HashEntry数组,这里的HashEntry和HashMap的Entry结构是一样的,关键在于Segment继承了ReentrantLock,且在获取,插入,扩容时使用了Unsafe类,保证了线程安全。
3.2ConcurrentHashMap属性解析
由于部分属性和HashMap一样,这里只介绍部分重要且不重复的。
segments:最外层的Segment数组
final Segment<K,V>[] segments;
DEFAULT_CONCURRENCY_LEVEL :默认并发等级,用来计算每个Segment中HashEntry数组的长度
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
RETRIES_BEFORE_LOCK :修改ConcurrentHashMap线程堵塞时允许重试次数
static final int RETRIES_BEFORE_LOCK = 2;
MAX_SEGMENTS :ConcurrentHashMap支持的最高并发级别
static final int MAX_SEGMENTS = 1 << 16;
这两个参数都是用来计算HashEntry存放于segments的位置
final int segmentShift;
final int segmentMask;
MIN_SEGMENT_TABLE_CAPACITY :segments内每个HashEntry数组的最小长度
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
SBASE:Segment[]类的基础偏移量,不理解的建议仔细阅读1.3.2
private static final long SBASE;
static{
...
SBASE = UNSAFE.arrayBaseOffset(Segment[].class);
...
}
TBASE:HashEntry[]类的基础偏移量,不理解的建议仔细阅读1.3.2
private static final long TBASE;
static{
...
TBASE= UNSAFE.arrayBaseOffset(HashEntry[].class);
...
}
SSHIFT:Segment[]类在内存地址中的比例因子转化为二进制首个1到末位的长度
private static final int SSHIFT;
static{
...
SSHIFT=31 - Integer.numberOfLeadingZeros(UNSAFE.arrayIndexScale(Segment[].class));
...
}
TSHIFT:HashEntry[]类在内存地址中的比例因子转化为二进制首个1到末位的长度
private static final int TSHIFT;
static{
...
TSHIFT=31 - Integer.numberOfLeadingZeros(UNSAFE.arrayIndexScale(HashEntry[].class));
...
}
MAX_SCAN_RETRIES :插入HashEntry时获取锁的最大尝试次数,如果CPU核心大于1就是64,否则1,(为啥我也不懂,欢迎读者补充)
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
3.3ConcurrentHashMap无参构造解析
默认调用有参构造
public ConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
为了方便理解,笔者将在注释中插入无参构造调用默认属性后值的前后变化
//initialCapacity:16,loadFactor:0.75f,concurrencyLevel:16
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
//判断加载因子,初始容量,并发级别是否和法,由于此时调用的是默认属性,显然都合法
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
//如果并发级别大于ConcurrentHashMap能处理的最高并发级别,默认等于2的16次方
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
//上面为源码的注释,大意就是需要2个参数用来算每个Segment中的HashEntry数组的长度
//下面就是为了得出长度而进行的一系列的计算,有点枯燥,请耐心看完
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}//sshift: 0 -> 4,ssize: 1 -> 16
//segmentShift: 0 -> 28
this.segmentShift = 32 - sshift;
//segmentMask: 0 -> 15
this.segmentMask = ssize - 1;
//保证Segment数组也就是segments长度在最大长度内
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//c = 16 / 16 ,即c = 1
int c = initialCapacity / ssize;
//如果 1 * 16 < 16 ,c++,条件不满足所以c还是1
if (c * ssize < initialCapacity)
++c;
// cap = 2 ;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
//保证segment内的HashEntry[]长度不小于最小长度,也就是2
while (cap < c)
cap <<= 1;
// create segments and segments[0]
//初始化第一个segments[0]位置的segment对象
//以后创建的新segment对象都将从segment[0]位置取
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
//创建最外层的Segment数组
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
//使用CAS操作将s0插入ss
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
//将ss赋值给segments
this.segments = ss;
}
3.4ConcurrentHashMap put方法解析
3.4.1put(),put Segment
public V put(K key, V value) {
Segment<K,V> s;
//这里可以看到,ConcurrentHashMap是不允许value为空的
if (value == null)
throw new NullPointerException();
//计算hash,见3.4.2
int hash = hash(key);
//经过无参构造初始化后这个各变量的值为,hash:0,segmentShift:28,segmentMask:4
//计算HashEntry插入segments位置的索引
//大致意思就是使用hash值的最高4位于0100进行于与操作
int j = (hash >>> segmentShift) & segmentMask;
//这里先解释这个if判断在做什么,再解释计算方法
//使用UNSAFE类的CAS操作判断segments[j]位置是否存在HashEntry[]
//如果为空则初始化该位置的segment,后续还会进行多次非空判断以保障线程安全
//这里的(j << SSHIFT) + SBASE和j*SSHIFT+SBASE结果是一样的,因为SSHIFT一定是2的倍数,读者可以自行参考1.3.2进行尝试
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
//见3.4.3
s = ensureSegment(j);
//插入segments[j]位置的HashEntry[],见3.4.4
return s.put(key, hash, value, false);
}
3.4.2hash()计算hash值,为计算索引值做准备
private int hash(Object k) {
//h=hash=0,获取哈希种子,途径和HashMap中的initHashSeedAsNeeded()方法类似
int h = hashSeed;
//如果h!=0,则重新计算hash值
if ((0 != h) && (k instanceof String)) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
//通过一系列的位移异或和相加操作使得hashcode的二进制码的每一位都参与到计算hash值当中,增加随机性
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
}
3.4.3ensureSegment()建立新的segment对象以插入HashEntry
private Segment<K,V> ensureSegment(int k) {
final Segment<K,V>[] ss = this.segments;
//获取segments[k]位置的偏移量
long u = (k << SSHIFT) + SBASE; // raw offset
Segment<K,V> seg;
//再次验证segment[k]位置元素是否为空,因为其他线程这时可能在同一位置进行插入操作
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
//获取segments[0]位置已经初始化好的segment对象
Segment<K,V> proto = ss[0]; // use segment 0 as prototype
//参数获取,容量,加载因子,阈值
int cap = proto.table.length;
float lf = proto.loadFactor;
int threshold = (int)(cap * lf);
//初始化segments[k]位置的segment的HashEntry[]
HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
//第三次判断segment[k]位置元素是否为空
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) { // recheck
Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
//第四次非空判断,循环直到没有线程占用时进行插入
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
break;
}
}
}
return seg;
}
3.4.4put(),put HashEntry
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
//尝试获取锁,获取不到的话使用类似lock()的方法重复尝试
HashEntry<K,V> node = tryLock() ? null :
//见3.4.5
scanAndLockForPut(key, hash, value);
V oldValue;
try {
//获取segments[j]位置的HashEntry[]对象
HashEntry<K,V>[] tab = table;
//计算插入HashEntry数组的位置
int index = (tab.length - 1) & hash;
//获取HashEntry[i]位置的HashEntry
HashEntry<K,V> first = entryAt(tab, index);
//从头节点开始遍历
for (HashEntry<K,V> e = first;;) {
//如果HashEntry[index]不为空
if (e != null) {
K k;
//存在相同的key则返回旧值并更新新值
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
//如果onlyIfAbsent为true则不更新新的value
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
//如果HashEntry[index]位置的HashEntry为空
else {
//如果tab[index]不为空
if (node != null)
//头插法
node.setNext(first);
else
//把first设置为node
node = new HashEntry<K,V>(hash, key, value, first);
//tab里面的HashEntry数量+1
int c = count + 1;
//判断此HashEntry[]是否需要扩容
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
//扩容,思路和HashMap相似,但是有两处细节不一样,这里简单解释一下
//1.如果链表长度为1,直接用老hash&新length转移node,而不重新计算hash
//2.长度不为1的话使用2个for循环将链表分为三段转移到扩容后的数组
//有可能转移后的链表还在原来的位置,也有可能第1段和第3段在i,而第2段在i<<1的位置
rehash(node);
//直接插
else
//使用UNSAFE.putOrderedObject将tab[index]位置的值改为node
setEntryAt(tab, index, node);
//修改次数加一
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
3.4.5scanAndLockForPut()智能获取锁,而非lock()暴力获取
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
//获取通过hash计算将要插入HashEntry[]位置的HashEntry
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
//初始锁获取尝试次数
int retries = -1; // negative while locating node
//如果获取不到锁则一直获取
while (!tryLock()) {
HashEntry<K,V> f; // to recheck first below
if (retries < 0) {
if (e == null) {
//如果e为空,也就是HashEntry[i]没有node,则new一个
if (node == null) // speculatively create node
node = new HashEntry<K,V>(hash, key, value, null);
retries = 0;
}
//如果e不为空且key重复,走另一分支
else if (key.equals(e.key))
retries = 0;
//e不为空key又不相等,往链表下面遍历
else
e = e.next;
}
//如果e不为空且key重复的分支
//判断++0是否大于64,如果大于使用lock()强行加锁,可能导致线程阻塞
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
//retries为偶数且通过hash计算出来的HashEntry[i]不等于自身了初始化retries
//翻译成人话就是获取锁的过程中这个first这个HashEntry被别的线程改了的话重新进行获取
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
e = first = f; // re-traverse if entry changed
retries = -1;
}
}
return node;
}
3.4.6hash(),hashCode计算hash
private int hash(Object k) {
int h = hashSeed;
if ((0 != h) && (k instanceof String)) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
}
3.5ConcurrentHashMap get方法解析
ConcurrentHashMap和HashMap一样,都是put难get简单,如果前面都看懂了那这里就是小学课本了。
public V get(Object key) {
Segment<K,V> s; // manually integrate access methods to reduce overhead
HashEntry<K,V>[] tab;
//寻找node在Segment[]的位置
int h = hash(key);
//寻找node在HashEntry[]的位置
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
//获取node在内存中的值
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
//遍历知道获取到想拿的key对应的value
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;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
总结
路漫漫其修远兮,吾将上下而求所。。。