7、原子累加器
累加器性能比较
private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
T adder = adderSupplier.get();
List<Thread> threads = new ArrayList<>();
long start = System.nanoTime();
for (int i = 0; i < 40; i++) {
threads.add(new Thread(() -> {
for (int j = 0; j < 500000; j++) {
action.accept(adder);
}
}));
}
threads.forEach(Thread::start);
threads.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(adder + " cost :" + (end - start) / 1000_000 + "ms");
}
比较AtomicLong 与 LongAdder
for (int i = 0; i < 5; i++)
demo(() -> new LongAdder(), adder -> adder.increment());
System.out.println();
for (int i = 0; i < 5; i++)
demo(() -> new AtomicLong(), adder -> adder.getAndIncrement());
输出
20000000 cost :54ms
20000000 cost :52ms
20000000 cost :45ms
20000000 cost :49ms
20000000 cost :50ms
20000000 cost :486ms
20000000 cost :462ms
20000000 cost :468ms
20000000 cost :451ms
20000000 cost :491ms
性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Thread-0累加Cell[0],Thread-1累加Cell[1]…最后将结果汇总。这样他们在累加时操作的不同Cell变量,因此减少了CAS重试失败,从而提高性能。
源码之LongAdder
LongAdder 是并发大师 @author Dong Lea(大哥李)的作品,设计的非常精巧
LongAdder类有几个关键域
// 累加单元数组,懒惰初始化
transient volatile Cell[] cells;
// 基础值,如果没有竞争,则用cas累加这个域
transient volatile long base;
// 在cells创建或扩容时,置为1,表示加锁
transient volatile int cellBusy;
cas锁
// 不要用于实践
public class LockCas {
private AtomicInteger state = new AtomicInteger(0);
public void lock() {
while (true) {
if (state.compareAndSet(0, 1))
break;
}
}
public void unlock() {
state.set(0);
}
}
测试
public static void main(String[] args) {
LockCas lockCas = new LockCas();
new Thread(() -> {
log.debug("begin");
lockCas.lock();
try {
log.debug("locked");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lockCas.unlock();
}
}, "t1").start();
new Thread(() -> {
log.debug("begin");
lockCas.lock();
try {
log.debug("locked");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lockCas.unlock();
}
}, "t2").start();
}
输出
2022/03/12-15:38:37.607 [t1] c.Test10 - begin
2022/03/12-15:38:37.607 [t2] c.Test10 - begin
2022/03/12-15:38:37.610 [t1] c.Test10 - locked
2022/03/12-15:38:38.619 [t2] c.Test10 - locked
原理之伪共享
其中Cell即为累加单元
// 防止缓存行伪共享
@sun.misc.Contended
static final class Cell {
volatile long value;
Cell(long x) { value = x; }
// 最重要的方法, 用来 cas 方式进行累加, prev 表示旧值, next 表示新值
final boolean cas(long prev, long next) {
return UNSAFE.compareAndSwapLong(this, valueOffset, prev, next);
}
// 省略不重要代码
}
得从缓存说起
缓存与内存的速度比较
因为CPU与内存的速度差异很大,苏姚靠预读数据至缓存来提升效率。
而缓存以缓存行为单位,每个缓存对应着一块内存,一般是64byte(8个long)
缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中
CPU要保证数据的一致性,如果某个CPU核心更改了数据,其他CPU核心对应的整个缓存行必须失效
因为Cell是数组形式,在内存中是连续存储的,一个Cell为24字节(16字节的对象头和6字节的value),因此缓存行可以存下两个的Cell对象。问题来了:
- Core-0要修改Cell[0]
- Core-1要修改Cell[1]
无论谁修改成功,都会导致对方Core的缓存行失效,比如Cell[0]中 Cell[0]=6000,Cell[1]=8000要累加Cell[0]=6001,Cell[1]=8001,这时会让Core-1的缓存行失效
@sun.misc.Contended用来解决这个问题,它的原理时在使用次注解的对象或字段的前后各增加128字节大小的padding,从而让CPU将对象予读至缓存时占用不同的缓存行,这样,不会造成对方缓存行的失效
累加主要调用下面的方法
public void add(long x) {
// as 为累加单元数组
// b 为基础值
// x 为累加值
Cell[] as; long b, v; int m; Cell a;
// 进入 if 的两个条件
// 1. as 有值, 表示已经发生过竞争, 进入 if
// 2. cas 给 base 累加时失败了, 表示 base 发生了竞争, 进入 if
if ((as = cells) != null || !casBase(b = base, b + x)) {
// uncontended 表示 cell 没有竞争
boolean uncontended = true;
if (
// as 还没有创建
as == null || (m = as.length - 1) < 0 ||
// 当前线程对应的 cell 还没有
(a = as[getProbe() & m]) == null ||
// cas 给当前线程的 cell 累加失败 uncontended=false ( a 为当前线程的 cell )
!(uncontended = a.cas(v = a.value, v + x))
) {
// 进入 cell 数组创建、cell 创建的流程
longAccumulate(x, null, uncontended);
}
}
}
add流程图
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
// 当前线程还没有对应的 cell, 需要随机生成一个 h 值用来将当前线程绑定到 cell
if ((h = getProbe()) == 0) {
// 初始化 probe
ThreadLocalRandom.current();
// h 对应新的 probe 值, 用来对应 cell
h = getProbe();
wasUncontended = true;
}
// collide 为 true 表示需要扩容
boolean collide = false;
for (; ; ) {
Cell[] as;
Cell a;
int n;
long v;
// 已经有了 cells
if ((as = cells) != null && (n = as.length) > 0) {
// 还没有 cell
if ((a = as[(n - 1) & h]) == null) {
// 为 cellsBusy 加锁, 创建 cell, cell 的初始累加值为 x
// 成功则 break, 否则继续 continue 循环
}
// 有竞争, 改变线程对应的 cell 来重试 cas
else if (!wasUncontended)
wasUncontended = true;
// cas 尝试累加, fn 配合 LongAccumulator 不为 null, 配合 LongAdder 为 null
else if (a.cas(v = a.value, ((fn == null) ? v + x : fn.applyAsLong(v, x))))
break;
// 如果 cells 长度已经超过了最大长度, 或者已经扩容, 改变线程对应的 cell 来重试 cas
else if (n >= NCPU || cells != as)
collide = false;
// 确保 collide 为 false 进入此分支, 就不会进入下面的 else if 进行扩容了
else if (!collide)
collide = true;
// 加锁
else if (cellsBusy == 0 && casCellsBusy()) {
// 加锁成功, 扩容
continue;
}
// 改变线程对应的 cell
h = advanceProbe(h);
}
// 还没有 cells, 尝试给 cellsBusy 加锁
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
// 加锁成功, 初始化 cells, 最开始长度为 2, 并填充一个 cell
// 成功则 break;
}
// 上两种情况失败, 尝试给 base 累加
else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x))))
break;
}
}
longAccumulate 流程图
每个线程刚进入longAccumulate时,会尝试对应一个Cell对象(找到一个坑位)
获得最终结果通过sum方法
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
8、Unsafe
概述
Unsafe对象提供了非常低层的,操作内存、线程的方法,Unsafe对象不能直接调用,只能通过反射获得
public class UnsafeAccessor {
static Unsafe unsafe;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new Error(e);
}
}
static Unsafe getUnsafe() {
return unsafe;
}
}
Unsafe CAS操作
class Student {
volatile int id;
volatile String name;
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) throws NoSuchFieldException {
Unsafe unsafe = UnsafeAccessor.getUnsafe();
Field id = Student.class.getDeclaredField("id");
Field name = Student.class.getDeclaredField("name");
// 获得成员变量的偏移量
long idOffset = UnsafeAccessor.unsafe.objectFieldOffset(id);
long nameOffset = UnsafeAccessor.unsafe.objectFieldOffset(name);
Student student = new Student();
// 使用 cas 方法替换成员变量的值
UnsafeAccessor.unsafe.compareAndSwapInt(student, idOffset, 0, 20); // 返回 true
UnsafeAccessor.unsafe.compareAndSwapObject(student, nameOffset, null, "张三"); // 返回 true
System.out.println(student);
}
使用自定义的AtomicDate实现直线线程安全的原子整数Account实现
public class AtomicData {
private volatile int data;
static final Unsafe unsafe;
static long DATA_OFFSET;
static {
unsafe = UnsafeAccessor.getUnsafe();
try {
DATA_OFFSET = unsafe.objectFieldOffset(AtomicData.class.getDeclaredField("data"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
public AtomicData(int data) {
this.data = data;
}
public void decrease(int amount) {
int oldValue;
while(true) {
oldValue = data;
if (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue, oldValue - amount))
return;
}
}
public int getData() {
return data;
}
}
Account实现
Account.demo(new Account() {
final AtomicData atomicData = new AtomicData(10000);
@Override
public Integer getBalance() {
return atomicData.getData();
}
@Override
public void withdraw(Integer amount) {
atomicData.decrease(amount);
}
});
本章小结
- CAS与volatile
- API
- 原子整数
- 原子引用
- 原子整数
- 字段更新器
- 原子累加器
- Unsafe
- *原理相关
- LongAdder 源码
- 伪共享