Atomic与Unsafe
Atomic
Atomic的类型
- 原子整数
- 原子引用
- 原子数组
- 字段更新器
- 原子累加器
原子整数
原子整数类型下的类
- AtomicInteger
- AtomicLong
- AtomicBoolean
因为上面这三个使用方式基本相同这里的例子只写AtomicInteger
AtomicInteger
AtomicInteger atomicInteger = new AtomicInteger();
//先获取再自增
int a = atomicInteger.getAndIncrement(); //0
System.out.println(a);
//先自增再获取
int b = atomicInteger.incrementAndGet(); //2
System.out.println(b);
//增加指定数值然后获取
int c = atomicInteger.addAndGet(3); //5
System.out.println(c);
//乘除或其他复杂运算
int i = atomicInteger.updateAndGet(x -> x * 10);
System.out.println(i);
原子引用
原子引用类型下的类
- AtomicReference
- AtomicMarkableReference
- AtomicStampedReference
AtomicReference
AtomicReference<BigDecimal> atomicReference = new AtomicReference<BigDecimal>(new BigDecimal("1.5"));
new Thread(() -> {
while (true){
BigDecimal bigDecimal = atomicReference.get();
BigDecimal next = bigDecimal.add(BigDecimal.valueOf(1.5));
if (atomicReference.compareAndSet(bigDecimal,next)){
break;
}
}
}).start();
new Thread(() -> {
while (true){
BigDecimal bigDecimal = atomicReference.get();
BigDecimal next = bigDecimal.add(BigDecimal.valueOf(1.5));
if (atomicReference.compareAndSet(bigDecimal,next)){
break;
}
}
}).start();
new Thread(() -> {
while (true){
BigDecimal bigDecimal = atomicReference.get();
BigDecimal next = bigDecimal.add(BigDecimal.valueOf(1.5));
if (atomicReference.compareAndSet(bigDecimal,next)){
break;
}
}
}).start();
Thread.sleep(1000);
System.out.println(atomicReference.get());
ABA问题
如果另一个线程修改V值假设原来是A,先修改成B,再修改回成A。当前线程的CAS操作无法分辨当前V值是否发生过变化。如果有其他线程动过了共享变量,那么自己CAS就算失败,可以使用AtomicStampedReference和AtomicMarkableReference。 AtomicStampedReference可以给原子引用加上版本号,追踪原子引用整个的变化过程。如果只关心是否更改过而不关心引用变量更新了几次可以使用AtomicMarkableReference。
AtomicStampedReference
public class AtomicDemo04 {
private static AtomicStampedReference<String> a = new AtomicStampedReference<>("A", 0);
public static void main(String[] args) throws InterruptedException {
String reference = a.getReference();
int stamp = a.getStamp();
update();
Thread.sleep(100);
boolean b = a.compareAndSet(reference, "B", stamp, stamp+1);
System.out.println(b);
}
private static void update() throws InterruptedException {
new Thread(() -> {
a.compareAndSet(a.getReference(),"B", a.getStamp(), a.getStamp()+1);
}, "t1").start();
Thread.sleep(100);
new Thread(() -> {
a.compareAndSet(a.getReference(),"A",a.getStamp(), a.getStamp()+1);
}, "t2").start();
}
}
AtomicMarkableReference
public class AtomicDemo05 {
private static AtomicMarkableReference<String> a = new AtomicMarkableReference<>("A",true);
public static void main(String[] args) throws InterruptedException {
String reference = a.getReference();
update();
Thread.sleep(100);
boolean b = a.compareAndSet(reference, "B", true, false);
System.out.println(b);
}
private static void update() throws InterruptedException {
new Thread(() -> {
a.compareAndSet(a.getReference(),"A", true,false);
}, "t1").start();
}
}
原子数组
原子引用类型下的类
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
AtomicIntegerArray
public class AtomicDemo07 {
public static void main(String[] args) throws InterruptedException {
demo(
() -> new int[10],
(array) -> array.length,
(array, index) -> array[index]++,
array -> System.out.println(Arrays.toString(array))
);
demo(
() -> new AtomicIntegerArray(10),
(array) -> array.length(),
(array, index) -> array.getAndIncrement(index),
System.out::println
);
}
private static <T> void demo(
Supplier<T> arraySupplier,
Function<T, Integer> lengthFun,
BiConsumer<T, Integer> putConsumer,
Consumer<T> printConsumer
) throws InterruptedException {
List<Thread> ts = new ArrayList<>();
T array = arraySupplier.get();
int length = lengthFun.apply(array);
for (int i = 0; i < length; i++) {
ts.add(new Thread(() -> {
for (int j = 0; j < 10000; j++) {
putConsumer.accept(array, j % length);
}
}));
}
ts.forEach(Thread::start);
Thread.sleep(3000);
printConsumer.accept(array);
}
}
字段更新器
使用字段更新器,只能配合volatile修饰的字段使用而且必须是public的
原子引用类型下的类
- AtomicReferenceFieldUpdater
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
AtomicReferenceFieldUpdater
public class AtomicDemo08 {
public static void main(String[] args) {
Student student = new Student();
AtomicReferenceFieldUpdater<Student, String> studentStringAtomicReferenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
String str = studentStringAtomicReferenceFieldUpdater.accumulateAndGet(student, "单国玉", (a,b)->{
System.out.println(a);
System.out.println(b);
return a;
});
System.out.println(str);
}
}
class Student {
public volatile String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
public Student() {
}
public Student(String name) {
this.name = name;
}
}
LongAdder
1.什么是LongAdder
LongAdder与AtomicLong是一样的都是Long自增但是LongAdder性能更好大概比AtomicLong高了好几倍。
LongAdder的思想
性能提升的原因,就是在有竞争时,设置多个累加单元,Thread-0累加Cell[0],而Thread-1 累加Cell[1]…最后将结果汇总。这样在累加时操作的不同的Cell变量,因此减少了CAS重试失败次数,从而提高性能(注意:累加单元不会多于CPU核心数)。
源码解析
1.Striped64
LongAdder的父类,是一个抽象类
- 重要属性
//当前计算机CPU数量,这个是控制cells数组的关键条件
static final int NCPU = Runtime.getRuntime().availableProcessors();
//cell数组
transient volatile Cell[] cells;
//没有发生竞争的时候,数据会累加到这个base身上
transient volatile long base;
//初始化cells或者扩容cells都需要获取锁,0表示无锁,1表示其他线程已经持有这把锁
transient volatile int cellsBusy;
- 静态内部类Cell
@sun.misc.Contended static final class Cell {
volatile long value;
Cell(long x) { value = x; }
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
private static final sun.misc.Unsafe UNSAFE;
private static final long valueOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> ak = Cell.class;
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}
- 重要方法
//第一个参数x:自增量
//第二个参数fn:函数式接口,一般是null,扩展使用
//第三个参数wasUncontended:是否发生过竞争,只有cells初始化之后,并且当前线程竞争修改失败,才会是false,其他情况都是true
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
//都有哪几种情况会进入这个方法:
//1.cells未初始化,也就是多线程写base发生竞争了——>会重试或初始化cells数组
//2.说明当前线程对应的cell为空,需要创建
//3.当前线程在CAS设置对应的cell的value的时候和其他线程发生了竞争导致CAS失败
//表示线程的hash值
int h;
//获取当前线程的hash值赋值给h
//如果hash值等于0说明当前线程还未分配hash值,会进入到代码块里进行分配hash值
//如果hash值不等于0说明当前线程已经分配hash值了
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current();
h = getProbe();
//因为默认情况下当前线程肯定是写入到了 cells[0]这个位置上,正常情况下肯定是会有竞争然后进入这个方法的,但是不会把这种情况当作真正的竞争情况。
wasUncontended = true;
}
//表示扩容意向,false一定不会扩容,true可能会扩容
boolean collide = false;
//自旋
for (;;) {
//as表示cells引用
//a表示当前线程命中的cell
//n表示celss数组的长度
//v表示期望值
Cell[] as; Cell a; int n; long v;
//表示cells已经初始化了,当前线程应该将数据写入到对应的cell中
if ((as = cells) != null && (n = as.length) > 0) {
//根据线程hash码去获取当前线程对应的cell如果是空则去创建
if ((a = as[(n - 1) & h]) == null) {
//cellsBusy == 0说明锁没有被占用
//cellsBusy!=0 说明有其他线程正在对cells数组进行扩容
if (cellsBusy == 0) {
//拿当前的值创建一个Cell
Cell r = new Cell(x);
//cellsBusy == 0——>表示当前锁未被占用可以去抢锁
//casCellsBusy()——>使用CAS去抢占锁
if (cellsBusy == 0 && casCellsBusy()) {
//是否创建成功的一个标记
boolean created = false;
try {
//rs——>cells数组引用
//m——>cells长度
//j——>当前线程命中的下标
Cell[] rs; int m, j;
//条件1与条件2恒成立
//rs[j = (m - 1) & h] == null——>再次做一次检查因为线程切换的原因这里的cell可能不为null
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
//向数组中添加元素和设置创建标记为true
rs[j] = r;
created = true;
}
} finally {
//释放锁
cellsBusy = 0;
}
if (created)
break;
continue;
}
}
//因为当前线程命中的cell为空还没有写入就扩容是不合理的
collide = false;
}
//当前线程对应的cell存在并且是因为第三种原因进入到这个方法才会走这个代码块
else if (!wasUncontended)
wasUncontended = true;
//当前线程重置过hash值,然后新命中的cell不为空则会来到这里
//当前线程会对新命中的cell值进行CAS设置,如果成功则退出
//如果CAS失败表示重置hash之后命中的cell也有竞争
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
//如果cells数组的长度大于等于CPU核心数则不在扩容(设置扩容意向为false)
//如果cells!=as说明其他线程已经扩容过了也设置扩容意向为不在扩容
else if (n >= NCPU || cells != as)
collide = false;
//!collide为ture表示设置扩容意向为true(只是意向而不是真的要去扩容)
else if (!collide)
collide = true;
//cells数组扩容逻辑逻辑
//cellsBusy == 0 ——>表示当前是无锁状态,当前线程可以去竞争这把锁
//casCellsBusy()——>使用CAS尝试去获取这把锁
else if (cellsBusy == 0 && casCellsBusy()) {
try {
//再次检查
if (cells == as) {
//创建一个新的Cell数组容量是原先的2倍
Cell[] rs = new Cell[n << 1];
//遍历老的数组把值放到新的数组当中
for (int i = 0; i < n; ++i)
rs[i] = as[i];
//新的引用赋值给全局变量
cells = rs;
}
} finally {
//释放锁
cellsBusy = 0;
}
collide = false;
continue;
}
//重置当前线程hash值,这会导致原先当前线程可能写入[0]的cell中重置后写道[1]的cell中
h = advanceProbe(h);
}
//celss还未初始化(as等于null)
//初始化cells数组
//条件1——>拿到锁
//条件2——>由于是并发的所以要在判断一下celss与as是否相等
//条件3——>尝试获取锁(把cellsBusy用CAS从0改为1)
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try {
//再次检查celss数组防止celss已经被其他线程初始化了
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
//1.获取锁失败表示其他线程正在初始化celss,celss已经被其他线程初始化,这两种情况都会导致进入到下面这个代码块中
//2.这个else if的逻辑就是把值累加到base上
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
}
}
2.LongAdder
- 重要方法add()
public void add(long x) {
//as 表示cells的引用
//b 表示获取的base值
//v 表示期望值
//m 表示cells数组的长度
//a 表示当前线程命中的cell单元格
Cell[] as; long b, v; int m; Cell a;
//条件1:true——>表示cells已经初始化过了,当前线程应该将数据写入到对应的cell中
// false——>表示cells未初始化,当前所有线程应该将数据写到base中
//条件2:true——>表示当前线程CAS成功
// false——>表示发生竞争了,可能需要重试或扩容
if ((as = cells) != null || !casBase(b = base, b + x)) {
//什么时候会进入这个代码块(满足一个条件就会进入这个代码块):
//1.cells已经初始化过了
//2.CAS发生竞争设置BASE失败
// true——>没有发生竞争
// false——>发生竞争
boolean uncontended = true;
// (as == null || (m = as.length - 1) < 0)——>看cells是否初始化
//结果为true——>说明cells未初始化,这个线程是因为CAS失败进入这个代码块的。
//结果为false——>说明cells已经初始化了,当前线程应该是找自己的cell写值。
//(a = as[getProbe() & m])——>看当前线程对应下标的cell是否为空
//getProbe()——>获取当前线程的hash值,m表示cells长度-1
//as[getProbe() & m]——>获得的值是小于等于celss长度-1
//a = as[getProbe() & m]) == null——根据一个下标获取cell
//(a = as[getProbe() & m])——>这个为true表示当前线程对应下标的cell为空,需要创建 longAccumulate支持
//(a = as[getProbe() & m])——>这个为false表示当前线程对应下标的cell不为空,不需要创建 下一步就是增加cell的value
//!(uncontended = a.cas(v = a.value, v + x))——>通过CAS修改当前线程对应的cell节点的value
//true表示CAS失败,意味着当前线程对应的cell有其他线程也在改发生了竞争
//false表示CAS设置成功,
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
//都有哪几种情况会调用这个方法:
//1.cells未初始化,也就是多线程写base发生竞争了——>会重试或初始化cells数组
//2.说明当前线程对应的cell为空,需要创建
//3.当前线程在CAS设置对应的cell的value的时候和其他线程发生了竞争导致CAS失败
longAccumulate(x, null, uncontended);
}
}
Unsafe
Unsafe介绍
Unsafe对象提供了非常底层的,操作内存,线程的方法,Unsafe对象不能直接调用,只能通过反射获得。
Unsafe对象的获取与CAS相关方法代码
/**
* @author 单国玉
* @date 2021/5/31
* @description Unsafe对象的获取以及CAS的相关方法
*/
public class UnsafeDemo01 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
Teacher t = new Teacher();
t.id=1;
t.name="单国玉";
//1.获取属性偏移量
long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));
//2.执行CAS操作
boolean b = unsafe.compareAndSwapInt(t, idOffset, 1, 2);
System.out.println(b);
boolean c = unsafe.compareAndSwapObject(t, nameOffset, "单国玉", "shanguoyu");
System.out.println(c);
System.out.println(t.toString());
}
}
class Teacher {
volatile int id;
volatile String name;
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}