不用锁来保护共享变量的线程安全,可以使用cas的原子类
compareSet:比较并设置,内部是原子的
为什么无锁效率高
- 无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而synchronized会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。打个比喻
- 线程就好像高速跑道上的赛车,高速运行时,速度超快,- -旦发生上下文切换,就好比赛车要减速、熄火,等被唤醒又得重新打火、启动、加速… 恢复到高速运行,代价比较大
●但无锁情况下,因为线程要保持运行,需要额外CPU的支持,CPU在这里就好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。
所以cas适合在多核cpu下,并且线程数不能超过cpu核数,线程数高了也跑不起来
CAS的特点
结合CAS和volatile可以实现无锁并发,适用于线程数少、多核CPU的场景下q
-
CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
-
synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
-
CAS体现的是无锁并发(并不是通过加锁的方式的来保护共享资源而是通过不断重试)、无阻塞并发(保持cas线程不断运行),请仔细体会这两句话的意思
-
因为没有使用synchronized, 所以线程不会陷入阻塞,这是效率提升的因素之一
-
但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响
累加器
比原子类的性能好,累加器专门用来做累加。
性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0 累加Cel[0],而Thread-l 累加Cel[1]…最后将结果汇总。这样它们在累加时操作的不同的Cell变量,因此减少了CAS重试失败,从而提高性能。
LongAdder
1、Cell在多线程累加的时候用自己线程cell单元,用于减少重试次数。
2、当没有线程竞争的时候,就没有必要创建cell而是使用base。
3、cas是无锁的,通过不断地重试来保证并发,那为什么还表示加锁呢?
总结:
以上三个变量都是呗volatile修饰的,并且都有transient关键字,表示序列化的时候不会被序列化。
原理之伪共享
其中Cell即为累加单元
Contended:竞争的意思
什么是缓存行伪共享?
读取不同的地方数据所需时间
而缓存以缓存行为单位,每个缓存行对应着一块内存,一般是64 byte (8 个long)
缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中
CPU要保证数据的一致性,如果某个CPU核心更改了数据,其它CPU核心对应的整个缓存行必须失效
因为失效是以缓存行为单位的,要失效就一起失效,为了解决这个问题就有了Contended注解(放置一个缓存行容纳多个cell对象)
原理:
在使用此注解的对象或字段的前后各增加128字节大小的padding,相当于添加空白数据将其填满一行,只让放一个cell对象
LongAdder源码-add
代码:
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
//判断累加单元的数组为空不为空(当没有竞争的时候cells数组为null,当竞争发生了才会去创建cells数组,从而在创建里面的累加单元)
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
//判断当前线程cell是否创建
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
当累加单元的数组或者累加单元没有创建或者创建失败就会进入longAccumulate
longAccumulate的实现:
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current(); // force initialization
h = getProbe();
wasUncontended = true;
}
boolean collide = false; // True if last slot nonempty
//死循环
for (;;) {
Cell[] as; Cell a; int n; long v;
//创建了cells数组,但是累加单元不够,用的时候才创建累加单元
if ((as = cells) != null && (n = as.length) > 0) {
//获取当前线程有没有对应的累加单元
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) { // Try to attach new Cell
//创建cell累加单元,它还未放进去cells数组
Cell r = new Cell(x); // Optimistically create
//判断别人上锁没,并尝试上锁
if (cellsBusy == 0 && casCellsBusy()) {
boolean created = false;
try { // Recheck under lock
Cell[] rs; int m, j;
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
//检查线程对应的cells的槽位是不是null
rs[j = (m - 1) & h] == null) {
//为空,将刚才自己创建的cell对象填到该槽位上
rs[j] = r;
created = true;
}
} finally {
cellsBusy = 0;
}
if (created)//当槽位不为空,就回到循环入口
break;
continue; // Slot is now non-empty
}
}
collide = false;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
//数组存在并且对应的累加单元也已经创建
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;//累加成功就返回,失败进入下面的else if
else if (n >= NCPU || cells != as)//判断数组的长度是否超过cpu核数
collide = false; //防止数组扩容
else if (!collide)//超过cpu核数的话,代码会走到这
collide = true;
//没有超过cpu核数,扩容的逻辑(先判断有没有加锁,再用cas的方式加锁)
else if (cellsBusy == 0 && casCellsBusy()) {
try {//扩容逻辑
if (cells == as) { // Expand table unless stale
Cell[] rs = new Cell[n << 1];//扩大为原来的2倍
for (int i = 0; i < n; ++i)
rs[i] = as[i];//将就数组拷贝到新数组里
cells = rs;//新数组替换旧数组
}
} finally {
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
h = advanceProbe(h);//改变线程对应的cell对象,在循环
}
//cells数组还不存在得情况(cellsBusy标记位,0标记未加锁,1标记加锁),没有其他线程去改变cells数组,casCellsBusy()以cas的方式将cellsBusy标记位从0改为1
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
//创建cells数组初始化其中的一个累加单元后退出循环
try { // Initialize table
if (cells == as) {//判断有没有其他的线程已经把cells数组创建好了,相等表示没有创建好
Cell[] rs = new Cell[2];//数组长度为2
rs[h & 1] = new Cell(x);//累加单元为1
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;//解锁
}
if (init)
break;//退出循环
}
//在上面的情况cas加锁失败时,用cas去base上累加,成功就返回,失败就回到循环入口
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break; // Fall back on using base
}
}