注:此文基于Android的ConcurrentHashMap
讲解ConcurrentHashMap之前,我先花点篇幅讲解下CAS的机制思想,因为ConcurrentHashMap的核心使用CAS。
CAS: (全称叫Compare And Swap)翻译过来是比较并交换。什么意思呢?其实它的思想就是在写时,先比较内存值(V)和旧的预期值(A)是否相等,如果相等则修改内存值为新值(B)并返回true;如果不相等则什么也不做返回false。CAS是可以确保原子操作的。
java中CAS通过UnSafe类实现,UnSafe类提供了硬件级别的原子操作,我们看下UnSafe源码:
public final class Unsafe {
//UnSafe单例实例
private static final Unsafe theUnsafe;
//无效字段的偏移地址
public static final int INVALID_FIELD_OFFSET = -1;
/**
ARRAY_XX_BASE_OFFSET代表某一类型的数组第一个元素的偏移地址
ARRAY_XX_INDEX_SCALE 代表某一类型的数组中元素的增量地址,比如int此值为4
*/
public static final int ARRAY_BOOLEAN_BASE_OFFSET;
public static final int ARRAY_BYTE_BASE_OFFSET;
public static final int ARRAY_SHORT_BASE_OFFSET;
public static final int ARRAY_CHAR_BASE_OFFSET;
public static final int ARRAY_INT_BASE_OFFSET;
public static final int ARRAY_LONG_BASE_OFFSET;
public static final int ARRAY_FLOAT_BASE_OFFSET;
public static final int ARRAY_DOUBLE_BASE_OFFSET;
public static final int ARRAY_OBJECT_BASE_OFFSET;
public static final int ARRAY_BOOLEAN_INDEX_SCALE;
public static final int ARRAY_BYTE_INDEX_SCALE;
public static final int ARRAY_SHORT_INDEX_SCALE;
public static final int ARRAY_CHAR_INDEX_SCALE;
public static final int ARRAY_INT_INDEX_SCALE;
public static final int ARRAY_LONG_INDEX_SCALE;
public static final int ARRAY_FLOAT_INDEX_SCALE;
public static final int ARRAY_DOUBLE_INDEX_SCALE;
public static final int ARRAY_OBJECT_INDEX_SCALE;
public static final int ADDRESS_SIZE;
/**
注册本地方法
*/
private static native void registerNatives();
private Unsafe() {
}
//获取UnSafe单例实例对象
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
/**
获取var1对象中偏移地址为var2的int类型的字段值
var1:包含需要读取字段值的对象
var2: var1中int类型字段的偏移地址
*/
public native int getInt(Object var1, long var2);
/**
设置var1对象中偏移地址为var2的int类型的字段值为var4
*/
public native void putInt(Object var1, long var2, int var4);
/**
获取var1对象中偏移地址为var2、类型为Object的字段值
*/
public native Object getObject(Object var1, long var2);
/**
设置var1对象中偏移地址为var2、类型为Object的字段值为var4
*/
public native void putObject(Object var1, long var2, Object var4);
/**
获取var1对象中偏移地址为var2、类型为boolean的字段值
*/
public native boolean getBoolean(Object var1, long var2);
/**
设置var1对象中偏移地址为var2、类型为boolean的字段值为var4
*/
public native void putBoolean(Object var1, long var2, boolean var4);
/**
获取var1对象中偏移地址为var2、类型为byte的字段值
*/
public native byte getByte(Object var1, long var2);
/**
设置var1对象中偏移地址为var2、类型为byte的字段值为var4
*/
public native void putByte(Object var1, long var2, byte var4);
/**
获取var1对象中偏移地址为var2、类型为short的字段值
*/
public native short getShort(Object var1, long var2);
/**
设置var1对象中偏移地址为var2、类型为short的字段值为var4
*/
public native void putShort(Object var1, long var2, short var4);
/**
获取var1对象中偏移地址为var2、类型为char的字段值
*/
public native char getChar(Object var1, long var2);
/**
设置var1对象中偏移地址为var2、类型为char的字段值为var4
*/
public native void putChar(Object var1, long var2, char var4);
/**
获取var1对象中偏移地址为var2、类型为long的字段值
*/
public native long getLong(Object var1, long var2);
/**
设置var1对象中偏移地址为var2、类型为long的字段值为var4
*/
public native void putLong(Object var1, long var2, long var4);
/**
获取var1对象中偏移地址为var2、类型为float的字段值
*/
public native float getFloat(Object var1, long var2);
/**
设置var1对象中偏移地址为var2、类型为float的字段值为var4
*/
public native void putFloat(Object var1, long var2, float var4);
/**
获取var1对象中偏移地址为var2、类型为double的字段值
*/
public native double getDouble(Object var1, long var2);
/**
设置var1对象中偏移地址为var2、类型为double的字段值为var4
*/
public native void putDouble(Object var1, long var2, double var4);
/**
获取偏移地址为var1、类型为byte的字段值
*/
public native byte getByte(long var1);
/**
设置偏移地址为var1、类型为byte的字段值为var3
*/
public native void putByte(long var1, byte var3);
/**
获取偏移地址为var1、类型为short的字段值
*/
public native short getShort(long var1);
/**
设置偏移地址为var1、类型为short的字段值为var3
*/
public native void putShort(long var1, short var3);
/**
获取偏移地址为var1、类型为char的字段值
*/
public native char getChar(long var1);
/**
设置偏移地址为var1、类型为char的字段值为var3
*/
public native void putChar(long var1, char var3);
/**
获取偏移地址为var1、类型为int的字段值
*/
public native int getInt(long var1);
/**
设置偏移地址为var1、类型为int的字段值为var3
*/
public native void putInt(long var1, int var3);
/**
获取偏移地址为var1、类型为long的字段值
*/
public native long getLong(long var1);
/**
设置偏移地址为var1、类型为long的字段值为var3
*/
public native void putLong(long var1, long var3);
/**
获取偏移地址为var1、类型为float的字段值
*/
public native float getFloat(long var1);
/**
设置偏移地址为var1、类型为float的字段值为var3
*/
public native void putFloat(long var1, float var3);
/**
获取偏移地址为var1、类型为double的字段值
*/
public native double getDouble(long var1);
/**
设置偏移地址为var1、类型为double的字段值为var3
*/
public native void putDouble(long var1, double var3);
/**
获取偏移地址为var1、类型为long的字段值
*/
public native long getAddress(long var1);
/**
获取偏移地址为var1、类型为long的字段值
*/
public native void putAddress(long var1, long var3);
/**
请求分配内存var1大的内存,返回分配的起始地址
*/
public native long allocateMemory(long var1);
/**
对偏移地址为var1的内存扩展var3的大小
*/
public native long reallocateMemory(long var1, long var3);
/**
*/
public native void setMemory(Object var1, long var2, long var4, byte var6);
public void setMemory(long var1, long var3, byte var5) {
this.setMemory((Object)null, var1, var3, var5);
}
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);
public void copyMemory(long var1, long var3, long var5) {
this.copyMemory((Object)null, var1, (Object)null, var3, var5);
}
/**
释放内存
*/
public native void freeMemory(long var1);
/**
获取静态域中给定字段var1的内存偏移地址
*/
public native long staticFieldOffset(Field var1);
/**
返回对象中给定静态字段的内存地址的偏移量,在这个类的其他方法中这个值只是被用作访问特定字段的方式。这个值对给定的字段是唯一的,并且后续对该方法的调用返回值都是一样的
*/
public native long objectFieldOffset(Field var1);
/**
返货静态域的起始内存地址
*/
public native Object staticFieldBase(Field var1);
/**
判断给定的类var1是否已经初始化,如果已经初始化则放回true,否则返回false
*/
public native boolean shouldBeInitialized(Class<?> var1);
/**
确定给定的类var1初始化,如果没有初始化则进行初始化,否则不做任何处理
*/
public native void ensureClassInitialized(Class<?> var1);
/**
返回给定类型为var1数组的起始地址
*/
public native int arrayBaseOffset(Class<?> var1);
/**
返回给定类型为var1数组的内存地址增量
*/
public native int arrayIndexScale(Class<?> var1);
/**
返回内存地址大小?不知道什么用
*/
public native int addressSize();
/**
返回内存分页大小,不知道什么用
*/
public native int pageSize();
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);
public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);
public native Object allocateInstance(Class<?> var1) throws InstantiationException;
public native void throwException(Throwable var1);
/**
CAS操作,如果对象var1中编译地址为var2的对象(对应的是内存值)与预期值的对象var4相等,则更改内存值为对象var5,并返回true;封装什么也不做并返回false
*/
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
/**
CAS操作,如果对象var1中编译地址为var2的int值(对应的是内存值)与预期值的int值var4相等,则更改内存值为int值var5,并返回true;封装什么也不做并返回false
*/
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
/**
获取对象var1中偏移地址为var2、类型为Object的字段值。此操作可以保证返回的值是内存中最新的值——可见性,即Volatile
*/
public native Object getObjectVolatile(Object var1, long var2);
/**
设置对象var1中偏移地址为var2、类型为Object的字段值为var4。此操作可以保证设置内存值之后值可见性,即Volatile
*/
public native void putObjectVolatile(Object var1, long var2, Object var4);
/**
获取对象var1中偏移地址为var2、类型为int的字段值。此操作可以保证返回的值是内存中最新的值——可见性,即Volatile
*/
public native int getIntVolatile(Object var1, long var2);
/**
设置对象var1中偏移地址为var2、类型为int的字段值为var4。此操作可以保证设置内存值之后值可见性,即Volatile
*/
public native void putIntVolatile(Object var1, long var2, int var4);
/**
获取对象var1中偏移地址为var2、类型为boolean的字段值。此操作可以保证返回的值是内存中最新的值——可见性,即Volatile
*/
public native boolean getBooleanVolatile(Object var1, long var2);
/**
设置对象var1中偏移地址为var2、类型为boolean的字段值为var4。此操作可以保证设置内存值之后值可见性,即Volatile
*/
public native void putBooleanVolatile(Object var1, long var2, boolean var4);
//其他getXXVolatile、setXXVolatile一样的意思,不一一说明
/**
释放被var1创建的一个在线程上的阻塞。这个方法可以被使用来终止之前调用var1导致的阻塞。
这个方法不是线程安全的,同时这个是java代码而不是native代码
*/
public native void unpark(Object var1);
/**
阻塞一个线程知道unpark出现、线程被中断、超时。如果一个unpark调用已经出现了,
* 这里只计数。timeout为0表示永不过期.当isAbsolute为true时,
* timeout是相对于新纪元之后的毫秒。否则这个值就是超时前的纳秒数。这个方法执行时
* 也可能不合理地返回(没有具体原因)
*/
public native void park(boolean isAbsolute, long time);
public native int getLoadAverage(double[] var1, int var2);
/**
获取对象var1中偏移地址为var2的值,如果内存中的int值与期望的值var5相等,则设置内存中的
值为var4+var5
*/
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var4));
return var5;
}
public final long getAndSetLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var4));
return var6;
}
public final Object getAndSetObject(Object var1, long var2, Object var4) {
Object var5;
do {
var5 = this.getObjectVolatile(var1, var2);
} while(!this.compareAndSwapObject(var1, var2, var5, var4));
return var5;
}
public native void loadFence();
public native void storeFence();
public native void fullFence();
private static void throwIllegalAccessError() {
throw new IllegalAccessError();
}
static {
//注册
registerNatives();
Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
//实例化
theUnsafe = new Unsafe();
// 获取各种类型数组的起始偏移地址及其增量地址
ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
ARRAY_SHORT_BASE_OFFSET = theUnsafe.arrayBaseOffset(short[].class);
ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class);
ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class);
ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class);
ARRAY_FLOAT_BASE_OFFSET = theUnsafe.arrayBaseOffset(float[].class);
ARRAY_DOUBLE_BASE_OFFSET = theUnsafe.arrayBaseOffset(double[].class);
ARRAY_OBJECT_BASE_OFFSET = theUnsafe.arrayBaseOffset(Object[].class);
ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class);
ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class);
ARRAY_SHORT_INDEX_SCALE = theUnsafe.arrayIndexScale(short[].class);
ARRAY_CHAR_INDEX_SCALE = theUnsafe.arrayIndexScale(char[].class);
ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale(int[].class);
ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class);
ARRAY_FLOAT_INDEX_SCALE = theUnsafe.arrayIndexScale(float[].class);
ARRAY_DOUBLE_INDEX_SCALE = theUnsafe.arrayIndexScale(double[].class);
ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);
ADDRESS_SIZE = theUnsafe.addressSize();
}
}
总的来说,UnSafe类提供的功能主要有:
1、内存管理:
- 分配内存:allocateMemory
- 扩展内存:reallocateMemory
- 释放内存:freeMemory
2、读写对象字段值 - 读:getXXX、objectFieldOffset、getXXXVolatile等
- 写:putXXX、putXXXVolatile等
3、线程挂起和恢复操作 - 挂起:park
- 恢复:unpark
4、CAS操作: compareAndSwapInt、compareAndSwapObject、compareAndSwapLong等
我们了解了CAS和UnSafe的原理之后,接着我们开始解析ConcurrentHashMap:
1、ConcurrentHashMap的数据结构是什么?
2、ConcurrentHashMap默认容量是多少?
3、ConcurrentHashMap最大容量是多少?
4、ConcurrentHashMap每次扩容是多少?
5、ConcurrentHashMap散列函数的实现是?如何计算出散列地址?
6、ConcurrentHashMap get的原理是?
7、ConcurrentHashMap put的原理是?
8、ConcurrentHashMap 是否是线程安全?
9、多线程环境下为什么ConcurrentHashMap比HashTable性能好?
10、ConcurrentHashMap与HashMap的区别是?
问题1:ConcurrentHashMap的数据结构是什么?
ConcurrentHashMap也是采用数组+单链表的数据结构,其使用的是内部类中的Node作为节点
/**
* 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;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
//...省略
}
通过上述代码,可以知道每个节点中hash值、key值是不可变的,而val和next是可见性的,即读写时均使用的内存中最新的值。
问题2:ConcurrentHashMap默认容量是多少?
与HashMap一样,其默认容量是16,看如下代码:
/**
* The default initial table capacity. Must be a power of 2
* (i.e., at least 1) and at most MAXIMUM_CAPACITY.
*/
private static final int DEFAULT_CAPACITY = 16;
问题3:ConcurrentHashMap最大容量是多少?
最大是其实是可以达到Integer.MAX_VALUE的,即使源码中定义了ConcurrentHashMap允许的最大容量,但是其在扩容的时候是不受这个最大容量的限制的,我们看下相关的源码:
private static final int MAXIMUM_CAPACITY = 1 << 30;
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
//n是当前散列表的大小
int n = tab.length, stride;
//...省略
//nextTab用于扩容时,存放移动或者拷贝原散列表元素,结束之后将nextTab赋值给table的
if (nextTab == null) { // initiating
try {
@SuppressWarnings("unchecked")
/**扩容大小每次为n*2,并没有使用最大值进行限制,所以时可以达到
Integer.MAX_VALUE的
*/
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;
}
//...省略
}
//...省略
}
上述代码是在扩容时,默认扩容的大小为原来容量的2倍,且不受定义中最大容量的限制,所以其最大容量其实是可以达到Integer.MAX_VALUE的
问题4:ConcurrentHashMap每次扩容是多少?
每次扩容时原来的容量的2倍,即:n << 1。同时android中的ConcurrentHashMap与HashMap不一样有个扩容阈值和加载因子。
问题5:ConcurrentHashMap散列函数的实现是?如何计算出散列地址?
此问题在问题6中回答
问题6:ConcurrentHashMap get的原理是?
我们先看get的源码部分:
public V get(Object key) {
/**
tab:散列表
e、p: 散列表中的节点
n:散列表容量大小
eh: 匹配节点的hash值
ek: 匹配节点的key值
h: 要查找的hash值
*/
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
//通过spread函数来对key的hashcode继续计算,得到key的hash值
int h = spread(key.hashCode());
// 通过(n -1)& h得到映射地址即对于的散列表下标
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
//如果散列表不为空,并且取下标处的节点不为空
/**
如果e代表的链表表头节点的hash值与要查找的has值相等,并且key相等,则代表表头节点就是查找
的,直接返回此节点val值
*/
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
// 如果hash值小0,在以表头为e的链表中查找匹配的节点并返回节点的值
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;
}
// 对key的hashcode进行(h ^(h>>>16)) & HASH_BITS操作,得到key的hash值
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
/**
通过UnSafe对象的getObjectVolatile方法去除数组tab中下标为((long)i << ASHIFT) + ABASE的节点,其
具有Volatile语义,即同步内存的值,取到的节点是内存中最新的值
*/
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);
}
get的原理总结来说是:
1、先计算出key的hash值:取key的hashcode进行(h ^ (h>>>16)) & HASH_BITS得到key的hash值
2、计算出映射地址及散列表的下标:对key的hash值进行h & (n - 1)操作得到映射地址
3、根据下标取出链表,再在链表中查找匹配的节点,如果找到节点则返回节点的val;如果找不到节点则返回null
到此我们可以很容易回答问题5中的问题:
散列函数的实现是:取key的hashcode,对hashcode进行(h ^ (h >>> 16)) & HASH_BITS操作得到hash值
是这样计算出映射地址的:对key的hash值进行h & (n - 1) 操作得到映射地址
问题7:ConcurrentHashMap put的原理是?
我们先看下ConcurrentHashMap中put的源码:
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 如果key或者value为null则抛出NullPointerException
if (key == null || value == null) throw new NullPointerException();
//计算出key的hash值
int hash = spread(key.hashCode());
//链表大小
int binCount = 0;
// 遍历散列表
for (Node<K,V>[] tab = table;;) {
/**
f: 散列表中的节点
n: 散列表数组的长度
i: 散列表的下标
fh: 节点的hash值
*/
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) {
/**
如果散列表中下标为i的节点为空,即散列表中不存在此键值对,则先创建节点,通过cas操作
将节点存入散列表的i位置,并退出循环
*/
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)
//如果散列表正在移动,及扩容操作,则使用helpTransfer方法协助其他线程一同扩容
tab = helpTransfer(tab, f);
else {
// 节点的旧值
V oldVal = null;
synchronized (f) {
// 从新从内存中取散列表i位置的节点
if (tabAt(tab, i) == f) {
//如果内存中最新的节点与f相等,则进行下列操作
if (fh >= 0) {
//如果hash值大于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) {
// 如果节点是树形结构,则通过树的方式进行put操作,在此不做说明
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;
}
}
else if (f instanceof ReservationNode)
throw new IllegalStateException("Recursive update");
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
// 进行扩容处理
addCount(1L, binCount);
return null;
}
put的原理总结如下:
1、计算出key的hash值
2、通过key的hash值计算出其对应的映射地址及散列表的下标
3、取出散列表下标处的链表,如果链表为空,代表是新的元素,通过cas进行添加操作
4、如果散列表正在扩容,则协助其他线程一起完成扩容操作,再重新进行开始步骤3的操作
5、接着通过遍历链表,判断是否已经存在要插入的元素,如果存在则更改此元素的值并返回旧的值
6、如果在链表中没有找到要插入的元素,则通过cas进行添加操作
7、如果是添加元素则还需要根据需要进行扩容操作
问题8:ConcurrentHashMap 是否是线程安全?
答案是肯定的,ConcurrentHashMap是线程安全的。通过上述代码可以看到ConcurrentHashMap使用了Volatile、cas、synchronized的语义,采用锁分离机制确保线程的安全。其中get的操作时通过volatile来确保读取的是内存中最新的值;put操作通过volatile找出对应的链表,通过cas的方式创建链表;通过synchronized遍历链表并进行元素值的更改或者添加元素操作。
问题9:多线程环境下为什么ConcurrentHashMap比HashTable性能好?
问题8中的分析,而HashTable加锁机制是在每个涉及读写操作的方法都使用synchronized修饰。所以ConcurrentHashMap的get操作是具有并发性的,put操作则具有局部并发功能,所以ConcurrentHashMap的性能远比HashTable好。
问题10:ConcurrentHashMap与HashMap的区别是?
主要区别是:
1、ConcurrentHashMap是线程安全类,而HashMap不是。所以在单线程中ConcurrentHashMap性能比HashMap的性能差,但ConcurrentHashMap在多线程中时安全的而HashMap在多线程是不是安全的
2、ConcurrentHashMah不允许key和value为null,而HashMap是允许的
3、ConcurrentHashMap与HashMap默认容量都是16
4、ConcurrentHashMap与HashMap每次扩容默认都是原来容量的2倍
网友评论