ConcurrentHashMap
多线程的情况下如何保证HashMap线程安全
- Collections.synchronizedMap(Map)创建线程安全的map
- 使用HashTable
- 使用ConcurrentHashMap
JDK1.7中的ConcurrentHashMap
- JDK1.7及之前版本的ConcurrentHashMap,在内部维护了一个分段锁类(Segment)
static final class Segment<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
// 和 HashMap 中的 HashEntry 作用一样,真正存放数据的桶
transient volatile HashEntry<K,V>[] table;
transient int count;
// 快速失败(fail—fast)使用
transient int modCount;
// 大小
transient int threshold;
// 负载因子
final float loadFactor;
}
在Segment中,使用了volatile修饰了参数table,且Segment继承与ReentrantLock
JDK1.7中如何做到线程安全
-
使用了分段锁技术
-
每次put的时候,先访问Segment,然后在进行操作
public V put(K key, V value) { Segment<K,V> s; //如果为空,抛出空指针异常 if (value == null) throw new NullPointerException(); //......省略 return s.put(key, hash, value, false); }
-
并发度由数组大小决定,例如当前大小16,那么并发度就是16
ConcurrentHashMap的Get方法是如何操作的
- 在HashMap的基础上,先定位到Segment,在定位到元素
- 在ConcurrentHashMap的get方法中,全程不需要加锁
JDK1.8中的ConcurrentHashMap
ConcurrentHashMap在JDK1.8中做了什么修改
- JDK1.8中抛弃了Segment分段锁,采用了CAS + synchronized来保证并发安全
CAS是什么
- CAS是一种乐观锁的实现方式
- 在线程读取数据时不加锁,写回数据的时候,会进行原值比较,查看原值是否被其他线程修改了
- 如果没修改,则写回
- 如果修改了,则重新读取,并执行流程
CAS是否一定安全
- 不一定,例如ABA问题,CAS就无法判断了
- ABA问题:初始值为A,线程B将A改为B,线程C将B改为A,此时线程A就无法判断是否修改过了
当CAS无法保证安全,那么应该如何解决
- 可以加入版本号、时间戳等信息用于辅助
为什么JDK1.8中使用了重量级锁synchronized
- 因为在JDK1.6中,JVM对synchronized进行了优化,由原来的重量级锁换成锁升级的方式。
- 锁升级最初是使用轻量级锁进行锁定的
什么是锁升级,锁升级的顺序是什么
- 先试用偏向锁,优先同一线程并再次获取锁
- 如果失败,使用CAS轻量级锁
- 如果失败,开始短暂自旋,防止线程被挂起
- 如果以上都失败,则升级为重量级锁
ConcurrentHashMap成员变量
sizeCtl -> 初始化和扩容时使用
/**
* sizeCtl取值范围:
* 0:默认
* -1:哈希表正在初始化
* >0:相当于HashMap中的threshold,扩容阈值
* <-1:代表有多个线程正在扩容(例如:-2就代表有(2-1=1)个线程正在扩容)
*/
private transient volatile int sizeCtl;
ConcurrentHashMap数据结构
Node类
static class Node<K,V> implements Map.Entry<K,V> {
//key计算出的hash值
final int hash;
//key值
final K key;
//value值
volatile V val;
//链表使用
volatile Node<K,V> next;
//.....省略
}
- 与HashMap不同的是,ConcurrentHashMap中的value和next属性加上了volatile修饰
- volatile:保证了可见性,即多线程运行时,强制读取此元素(共享变量),是一种多线程中的轻量级同步机制
ConcurrentHashMap的初始化
ConcurrentHashMap构造方法
/**
* 默认空构造方法,创建大小为默认值(16)
*/
public ConcurrentHashMap() {}
/**
* 传入数组大小的构造方法
* @param: initialCapacity 数组大小
*/
public ConcurrentHashMap(int initialCapacity) {
//创建的大小为传入值(initialCapacity),负载因子为默认(0.75f)
//1为预计参与运行的线程数(在jdk1.8没有作用了)
this(initialCapacity, LOAD_FACTOR, 1);
}
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
this.sizeCtl = DEFAULT_CAPACITY;
putAll(m);
}
/**
* 传入数组大小及负载因子的构造方法
* @param: initialCapacity 数组大小
* @param: loadFactor 负载因子
*/
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
/**
* ConcurrentHashMap初始化真正运行的方法
* @param: initialCapacity 数组大小
* @param: loadFactor 负载因子
* @param: concurrencyLevel预计参与运算的线程数(JDK1.7中使用,用于设置分段锁Segment的数量,JDK1.8中无作用)
*/
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
//负载因子小于0,或数组大小小于0,或参与运算的线程数小于0,抛出异常
if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
//如果数组大小小于运行的线程数
if (initialCapacity < concurrencyLevel)
//设置大小为传入的线程数
initialCapacity = concurrencyLevel;
//使用负载因子对数组容量进行计算,实际上手动传入的loadFactor只在此处使用
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
//将计算出的size进行转换,若大于最大值,则设置为最大值,否则使用tableSizeFor进行转换,返回的结果为最接近size的2的幂数
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);
//设置sizeCtl,表示设置数组大小
//这个构造方法并没有真正的初始化ConcurrentHashMap,这里只是为了初始化而做准备
this.sizeCtl = cap;
}
/**
* 返回大于输入参数且最接近的2的幂数
*/
private static final int tableSizeFor(int c) {
int n = -1 >>> Integer.numberOfLeadingZeros(c - 1);
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
ConcurrentHashMap真正的初始化方法
/**
* ConcurrentHashMap真正初始化方法
* 只有在对数据进操作时才调用(putVal,merge方法等)
*/
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
//当table数组为空时,才进行循环初始化,否则直接返回
while ((tab = table) == null || tab.length == 0) {
//如果sizeCtl小于0,表示有其他线程正在对数组进行扩容
if ((sc = sizeCtl) < 0)
//释放当前线程资源,重新获取
Thread.yield();
//U.compareAndSetInt()为CAS的方法
//使用CAS,将sizeCtl变为-1,表示当前线程正在对数组进行初始化
else if (U.compareAndSetInt(this, SIZECTL, sc, -1)) {
try {
//数组为空才进行操作
if ((tab = table) == null || tab.length == 0) {
//DEFAULT_CAPACITY为数组默认大小16
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
//初始化Node数组
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
//设置扩容阈值,等同于HashMap中的LOAD_FACTOR
//这里等于n * 0.75
sc = n - (n >>> 2);
}
} finally {
//设置sizeCtl值
sizeCtl = sc;
}
break;
}
}
return tab;
}
ConcurrentHashMap取值
计算index值
static final int spread(int h) {
//HASH_BITS = 0x7fffffff
//为了保证计算后的值为正数
return (h ^ (h >>> 16)) & HASH_BITS;
}
为什么要与HASH_BITS进行计算?
- 为了保证计算后的值为正数,在二进制中,第一位为0代表正数,为1代表负数
get()方法
/**
* 通过key寻找value
*/
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
//使用key的hash值获取key的index
int h = spread(key.hashCode());
//当数组不为空,且当前index位置有值的时候,才进行寻找
if ((tab = table) != null && (n = tab.length) > 0 &&
//这里的e调用了tabAt()方法
(e = tabAt(tab, (n - 1) & h)) != null) {
//hash相等
if ((eh = e.hash) == h) {
//此时为数组,获取首节点
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
//此时为红黑树
else if (eh < 0)
//使用Node中的find方法寻找,见下方
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;
}
tabAt()方法
/**
* 寻找数组在内存中index位置的数据
* 使用Unsafe,直接操作内存
*/
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);
}
Node中的find()方法
/**
* @param: h hash值
* @param: k key值
*/
Node<K,V> find(int h, Object k) {
//获取当前的Node类
Node<K,V> e = this;
if (k != null) {
do {
K ek;
//Key值相等且hash相等,直接返回当前的Node类
if (e.hash == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
} while ((e = e.next) != null);
}
return null;
}
ConcurrentHashMap插入值
/**
* 对外开放的put方法
* @param: key key值
* @param: value value值
*/
public V put(K key, V value) {
//内部调用真正的赋值方法
return putVal(key, value, false);
}
/**
* 真正的赋值方法
* @param: key key值
* @param: value value值
* @param: onlyIfAbsent
*/
final V putVal(K key, V value, boolean onlyIfAbsent) {
//ConcurrentHashMap不允许null值,如果为null抛出异常
if (key == null || value == null) throw new NullPointerException();
//获取key的hash值
int hash = spread(key.hashCode());
//当为链表的时候,表示为链表的长度
//当为红黑树时,固定为2,保证put后进行扩容检查,且不触发红黑树转换
int binCount = 0;
//这里是死循环
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh; K fk; V fv;
//如果当前数组为空,则初始化ConcurrentHashMap
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//i = (n - 1) & hash) 这里为获取key的index值
//tabAt(tab, i = (n - 1) & hash)这里为寻找当前数组中,该index处的值
//如果当前位置为空,直接插入
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//使用CAS插入头元素,防止其他线程修改
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
break;
}
//MOVED = -1
//这里表示其他线程正在进行扩容
else if ((fh = f.hash) == MOVED)
//调用helpTransfer(),帮助扩容
tab = helpTransfer(tab, f);
//put方法默认传入的onlyIfAbsent为false,所以不走这段代码
// check first node without acquiring lock(官方注释),检查头元素但不获取锁
else if (onlyIfAbsent
&& fh == hash
&& ((fk = f.key) == key || (fk != null && key.equals(fk)))
&& (fv = f.val) != null)
return fv;
//这里为出现hash冲突,转换为树或链表
else {
//临时变量,存储旧元素
V oldVal = null;
//参数f加锁,f为原数组中的该位置的节点
synchronized (f) {
//cas重新获取当前数组中index位置的值,保证锁住的是第一个节点
//判断是否与f相等,若不相等,代表有其他线程操作过这里了,停止本次操作,重新循环
if (tabAt(tab, i) == f) {
//这里为链表
//因为第一次put值,ConcurrentHashMap要进行初始化,这里的判断是为了判断初始化/扩容是否完成了
if (fh >= 0) {
//这里是链表,赋值为1
binCount = 1;
//binCount改变为链表长度
for (Node<K,V> e = f;; ++binCount) {
K ek;
//判断key是否相等
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
//如果key相等,先保存原始值
oldVal = e.val;
//onlyIfAbsent传入为false,所以这里永远为true
if (!onlyIfAbsent)
//value值替换
e.val = value;
break;
}
Node<K,V> pred = e;
//链表循环到最后一个元素,且key值不存在,在这里进行插入
if ((e = e.next) == null) {
//插入链表尾部
pred.next = new Node<K,V>(hash, key, value);
break;
}
}
}
//这里为红黑树
else if (f instanceof TreeBin) {
Node<K,V> p;
//红黑树默认为2
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) {
//链表长度大于等于8,转为红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//扩容方法,此处用于检查是否需要扩容
addCount(1L, binCount);
return null;
}
ConcurrentHashMap扩容机制
addCount(),扩容方法
/**
* 扩容方法
* 这里的扩容方法分为两部分
* 1.更新baseCount计数器
* 2.扩容
* @param: x baseCount增加的数量
* @param: check 链表/树的长度
*/
private final void addCount(long x, int check) {
CounterCell[] cs; long b, s;
//更新baseCount
//如果counterCells计数器为空,就运行后面的CAS进行baseCount++操作
if ((cs = counterCells) != null ||
//这里相当于做了baseCount++操作
//如果CAS操作失败,就通过counterCells进行记录
!U.compareAndSetLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell c; long v; int m;
boolean uncontended = true;
//如果counterCells为空
if (cs == null || (m = cs.length - 1) < 0 ||
//通过一个随机数(ThreadLocalRandom.getProbe() & m)来获取一个inedx
//如果这个cs[index]位置的参数为空
(c = cs[ThreadLocalRandom.getProbe() & m]) == null ||
//CAS修改上面的随机出来的值,如果失败,说明出现并发情况
!(uncontended = U.compareAndSetLong(c, CELLVALUE, v = c.value, v + x))) {
//重试
fullAddCount(x, uncontended);
return;
}
//check为链表长度,如果小于1,则不考虑扩容
if (check <= 1)
return;
//统计ConcurrentHashMap数量
s = sumCount();
}
//check >= 0,则检查是否需要扩容
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
//如果当前数组的数量(s)大于数组扩容的阈值(sizeCtl)
//且当前数组不为空
//且数组长度小于数组最大容量
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
//获取一个关于n的标识
int rs = resizeStamp(n);
//sc小于0,代表有其他线程正在扩容
if (sc < 0) {
//这里的1,4,5条件,都代表扩容已经完成,不需要再次扩容
if ((sc >>> RESIZE_STAMP_SHIFT) != rs ||
sc == rs + 1 ||
sc == rs + MAX_RESIZERS ||
(nt = nextTable) == null ||
transferIndex <= 0)
break;
//尝试帮助进行扩容,并使用CAS将正在执行的扩容数+1
if (U.compareAndSetInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
//这里为没有线程正在扩容,尝试让自己成为第一个执行扩容的线程
else if (U.compareAndSetInt(this,SIZECTL,sc,(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
//统计ConcurrentHashMap数量
//这里用作判断是否要开启下一轮扩容
s = sumCount();
}
}
}
transfer(),真正的扩容方法
/**
* 真正的扩容方法
* 用于移动或复制数据到新数组中
* @param: tab 原数组
* @param: nextTab 新数组
*/
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
//MIN_TRANSFER_STRIDE = 16
//NCPU = Runtime.getRuntime().availableProcessors(),获取当前电脑的CPU处理器个数
//这里将数组的(长度/8)/CPU核数,若结果小于16,就默认为16
//为了让每个CPU处理同样的数量,避免数据分配不均
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE;
//这里代表新的数组并未初始化,处于第一次扩容
if (nextTab == null) {
try {
//n<<1 -> 扩容为2倍
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
//更新数组
nextTab = nt;
} catch (Throwable ex) {
//如果出现异常,则扩容失败
//数组容量sizeCtl直接使用最大值
sizeCtl = Integer.MAX_VALUE;
return;
}
//nextTable为成员变量,更新为刚才初始化的内容
nextTable = nextTab;
//transferIndex为扩容进度
transferIndex = n;
}
//获取新数组的长度
int nextn = nextTab.length;
//创建一个特殊节点,表示这个节点正在进行扩容
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
//控制是否继续处理下一个桶,true表示处理完当前的桶,可继续迁移下一组数据
boolean advance = true;
//用于控制扩容何时结束,也用于最后一个线程完成后,检查是否有遗漏的数据
boolean finishing = false;
//这里是死循环
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
while (advance) {
int nextIndex, nextBound;
//i-1,判断是否大于bound
//如果小于bound,则表示上次领取的任务完成了(上组数据迁移完成)
//如果大于bound,或扩容完成了,则修改状态为false,结束操作
if (--i >= bound || finishing)
advance = false;
//如果transferIndex<=0,表示数组分配的hash桶已经被分配完成了。
else if ((nextIndex = transferIndex) <= 0) {
//这里将i设置为-1,为后面使用,退出循环
i = -1;
advance = false;
}
//CAS修改transferIndex(length-区间值(stride)),留下的区间给其他线程使用
else if (U.compareAndSetInt(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {
//线程可处理区间最小下标
bound = nextBound;
//对i赋值,i为线程可处理区间的最大下标
i = nextIndex - 1;
//防止数据没有复制
advance = false;
}
}
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
if (U.compareAndSetInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
finishing = advance = true;
i = n; // recheck before commit
}
}
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
else {
synchronized (f) {
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
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);
}
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
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;
}
}
}
}
}
}
sumCount(),统计map内的元素数量
/**
* 统计ConcurrentHashMap的元素个数
* 类似于size()
* 这里统计counterCells的数量+baseCount
*/
final long sumCount() {
CounterCell[] cs = counterCells;
long sum = baseCount;
if (cs != null) {
for (CounterCell c : cs)
if (c != null)
sum += c.value;
}
return sum;
}
resizeStamp()
/**
* 生成一个关于n的标识
*/
static final int resizeStamp(int n) {
//RESIZE_STAMP_BITS = 16
//(1 << (RESIZE_STAMP_BITS - 1) = (1 << 15),二进制位高16位为0,低16位为1
return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
ConcurrentHashMap的其他方法
containsKey()
/**
* 判断当前key是否存在
* @param: key key值
*/
public boolean containsKey(Object key) {
//调用get()方法
return get(key) != null;
}
containsValue()
/**
* 判断当前value是否存在
* @param: value value值
*/
public boolean containsValue(Object value) {
//如果value为空,抛出异常
if (value == null)
throw new NullPointerException();
Node<K,V>[] t;
//当前数组不为空,才进行寻找
if ((t = table) != null) {
//Traverser 一个内部类,作用大致等用与迭代器
Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
for (Node<K,V> p; (p = it.advance()) != null; ) {
V v;
if ((v = p.val) == value || (v != null && value.equals(v)))
return true;
}
}
return false;
}
ConcurrentHashMap常见面试题
谈谈你理解的 Hashtable,讲讲其中的 get put 过程。ConcurrentHashMap同问。
- 见上方
1.8 做了什么优化?
- 取消了分段锁,采取了CAS+synchronized
- 加入了红黑树
线程安全怎么做的?
- 在操作数据的代码部分加锁(synchronized)
- 采用锁升级技术
不安全会导致哪些问题?
- 多线程同时操作出现的数据不一致问题
- 如果数据结构为链表,可能出现死锁
如何解决?有没有线程安全的并发容器?
- Collections.synchronizedMap(Map)创建线程安全的map
- 使用HashTable
- 使用ConcurrentHashMap
ConcurrentHashMap 是如何实现的?
- 在HashMap的基础上加锁
ConcurrentHashMap并发度为啥好这么多?
- 采用分段锁技术(CAS+synchronized)
1.7、1.8 实现有何不同?为什么这么做?
- 1.7采用分段锁
- 1.8采用CAS+synchronized
CAS是啥?
- 乐观锁的一种实现方式
- 当数据准备修改时,乐观的认为不会出现安全问题,当修改完准备保存时,将此时旧数据与修改前的旧数据进行比较,如果一致,则保存,否则放弃本次修改,重新读取流程。
ABA是啥?场景有哪些,怎么解决?
- ABA问题:初始值为A,线程B将A改为B,线程C将B改为A,此时线程A就无法判断是否修改过了
- 解决方法:加入版本号、时间戳等进行辅助
synchronized底层原理是啥?
synchronized锁升级策略
- 偏向锁
- CAS轻量级锁
- 自旋锁
- 重量级锁
快速失败(fail—fast)是啥,应用场景有哪些?安全失败(fail—safe)同问。
- fail-fast:
- java的一种安全机制
- 当线程A遍历数据,线程B改变数据结构时,此时线程A访问数据,会抛出异常
- 使用场景:java.util.concurrent包下的集合类都使用这种机制
- fail-safe:
- java的一种安全机制
- 当线程A遍历数据,其他线程改变数据结构时,线程A无法检测到改变
- 使用场景:java.util包下的集合类使用这种机制