原子累加器。
原子整数也可以做累加的。
---
性能比较,代码。
代码:
package cn.itcast.test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class Test41 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
demo(
() -> new AtomicLong(0),
(adder) -> adder.getAndIncrement()
);
}
for (int i = 0; i < 5; i++) {
demo(
() -> new LongAdder(),
adder -> adder.increment()
);
}
}
/*
() -> 结果 无参有结果的 提供累加器对象
(参数) -> 执行累加操作
*/
private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
T adder = adderSupplier.get();
List<Thread> ts = new ArrayList<>();
// 4 个线程,每人累加 50 万
for (int i = 0; i < 4; i++) {
ts.add(new Thread(() -> {
for (int j = 0; j < 500000; j++) {
action.accept(adder);
}
}));
}
long start = System.nanoTime();
ts.forEach(t -> t.start());
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(adder + " cost:" + (end - start) / 1000_000);
}
}
第一个是cas的。
第二个是LongAddr。有竞争设置多个共享单元的。
jvm反复执行很多次之后才会做优化的。
累加单元不会超过cpu的核心数,cas超过cpu的核心数是没有意义的。
---06---18---
LongAddr原理。
实现一个伪锁:
这个cas的0,也就是期望值是state的最新值,在主线程读取的。
unlock是锁的持有者才会解锁,所以不需要cas了。
这个方法是不行的,while会不断的消耗cpu。
测试:
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicInteger;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test42")
public class LockCas {
// 0 没加锁
// 1 加锁
private AtomicInteger state = new AtomicInteger(0);
public void lock() {
while (true) {
if (state.compareAndSet(0, 1)) {
break;
}
}
}
public void unlock() {
log.debug("unlock...");
state.set(0);
}
public static void main(String[] args) {
LockCas lock = new LockCas();
new Thread(() -> {
log.debug("begin...");
lock.lock();
try {
log.debug("lock...");
sleep(1);
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
log.debug("begin...");
lock.lock();
try {
log.debug("lock...");
} finally {
lock.unlock();
}
}).start();
}
}
结果:
---06---19-20---
cellsBusy类似于cas锁,作为加锁的标记。
这个锁是cells数组扩容时候的锁,或者创建cells数组的时候。
源码:
当pre的值和value的值相等的时候,会把next赋值给value。
看下这个注解:
如果一个缓存失效,失效是整个缓存行失效,一个缓存行可能是两个cell。
一个线程对应一个cpu的核心,一个线程对应一个累加单元。
失效是缓存行为单位的,要失效都一起失效,要想不一起失效就放在不同的缓存行。
对象头是16个字节的。
1个cell是24字节的。一个缓存行是64字节。数组是连续的读就一起读进去,可以存两个cell。不管改哪个都会让对方失效,带来效率的降低。
注解是防止缓存行容纳多个cell对象。
---06--21---
我们分析下源码:
第一步:看下increment方法。
第二步:
cells数组是懒惰创建的。
cell是懒惰创建的,没有竞争的话就是往基础值累加。
代码解读:
判断cells数组是不是空的。
cell为空没有竞争的时候就往基础值累加。
基础累计值成功了就不会进入if。
失败了进入if。
第三步:进入if。
这个逻辑是判断当前的线程有没有一个对应的累加单元cell。
---
当前的线程有一个累加单元了创建了。
a是创建好的累加单元,有的话就cas累加了。
没有就扩容。进入数组的创建:
---06--22---
学习扩容的流程。
什么时候进入这个方法,
1.累加单元数组
2.累加单元没有创建
3.创建但是累加没有成功。
死循环不断的尝试去获得累加单元
几种情况:累加单元没有创建。竞争失败累加不成功。
第一种情情况就是cell数组还是没有的,注意这个是没有的情况:
cellsBusy的0是没有加锁,1是加锁。因为我们创建cells数组要加锁的所以要判断下是不是加锁了。
as是最初读的数组的引用,cells是新的,cells==as表示别人还没有更改。
状态位由0改为1,1为加锁成功了。
加锁失败了,就是去加单值。
失败了进入循环入口,下一轮尝试。
-----------------------------------------------------------------------------------------
加锁成功了呢?
就是这个为true的时候。
cells==as表示没有别人创建好了。
数组大小是2放在0或者1上。
---06--24-
第二种情况,数组创建好了,但是累加单元还没好的。一个线程一个累加单元。
获取当前的线程看有没有累加单元,没有累加单元就要创建的,
数组存在cell没有创建。
创建cell对象,箭头还没有放在数组里面去。
没上锁尝试上锁,失败走到循环入口。
加锁成功,放在数组的槽位:
检查槽位是不是为空。 不为空就被别人侵占了。
就白创建了就进入下一次循环重试。
成功了就break。
---181---
第三种情况:数组创建了,线程的累加单元创建好了。
a是存在的。
否则,检查是不是超过了cpu的上限,再扩容就没有意义了,走扩容得逻辑,超过上限就设置标志位防止走扩容的逻辑:
是不是超过cpu上限。
没超过cpu上限:
进入扩容的逻辑,翻倍扩容的,成功在次循环。
n<<1为n*2。
不行换累加单元cell。
---182---
初始值在基础累加单元开始累加。
---183---