CAS (CompareAndSwap 比较和交换)
定义
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。
缺点:CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。
- 循环时间长开销很大。(CPU 开销大)
- 只能保证一个共享变量的原子操作。(多个使用锁)
- ABA问题。(不影响使用可不考虑)
AtomicInteger i = new AtmoicInteger();
i.incrementAndGet();//自增并返回结果
//incrementAndGet() 方法跟踪进去会发现底层是调用的native方法 ( Unsafe.class类中的
//public final native boolean compareAndSwapInt(...) )
//最终执行的就是linux底层的一条汇编指令 lock cmpxchg 指令 硬件支持(锁定北桥信号)
//synchronized 和 volatile 最终底层都是使用 lock cmpxchg 指令
ABA问题
线程1读取了数据0做++,回来校验原值时,这个过程中,其他线程对数据0做了修改,只是最终结果又变成了0,即原值为A,过程中被改为了B,最终又被改为了A,就是ABA问题
/**
* @Author blackcat
* @version: 1.0
* @description:ABA 问题
*/
@Slf4j
public class ABATest {
static AtomicReference<String> ref = new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
log.debug("main start...");
// 获取值 A
// 这个共享变量被它线程修改过?
String prev = ref.get();
other(); //
sleep(2);
// 尝试改为 C
log.debug("change A->C {}", ref.compareAndSet(prev, "C"));
}
private static void other() throws InterruptedException {
new Thread(() -> {
log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B"));
}, "t1").start();
sleep(1);
new Thread(() -> {
log.debug("change B->A {}", ref.compareAndSet(ref.get(), "A"));
}, "t2").start();
}
private static void sleep(int i) {
try {
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
[main] DEBUG com.blact.cat.first.AtomicTest - main start...
[t1] DEBUG com.blact.cat.first.AtomicTest - change A->B true
[t2] DEBUG com.blact.cat.first.AtomicTest - change B->A true
[main] DEBUG com.blact.cat.first.AtomicTest - change A->C true
**解决办法:**数据0加上版本号,每个线程读取数据0时连同版本号一起读取, 每次处理完回来比较的时候连同版本号一起比较即可。 AtomicStampedReference 可带版本
/**
* @Author blackcat
* @create 2021/8/2 14:17
* @version: 1.0
* @description:
*/
@Slf4j
public class AtomicStampedReferenceTest {
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
public static void main(String[] args) throws InterruptedException {
log.debug("main start...");
// 获取值 A
String prev = ref.getReference();
// 获取版本号
int stamp = ref.getStamp();
log.debug("版本 {}", stamp);
// 如果中间有其它线程干扰,发生了 ABA 现象
other();
sleep(2);
// 尝试改为 C
log.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
}
private static void other() throws InterruptedException {
new Thread(() -> {
log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B",
ref.getStamp(), ref.getStamp() + 1));
log.debug("更新版本为 {}", ref.getStamp());
}, "t1").start();
sleep(1);
new Thread(() -> {
log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A",
ref.getStamp(), ref.getStamp() + 1));
log.debug("更新版本为 {}", ref.getStamp());
}, "t2").start();
}
private static void sleep(int i) {
try {
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
[main] DEBUG com.blact.cat.first.AtomicStampedReferenceTest - main start...
[main] DEBUG com.blact.cat.first.AtomicStampedReferenceTest - 版本 0
[t1] DEBUG com.blact.cat.first.AtomicStampedReferenceTest - change A->B true
[t1] DEBUG com.blact.cat.first.AtomicStampedReferenceTest - 更新版本为 1
[t2] DEBUG com.blact.cat.first.AtomicStampedReferenceTest - change B->A true
[t2] DEBUG com.blact.cat.first.AtomicStampedReferenceTest - 更新版本为 2
[main] DEBUG com.blact.cat.first.AtomicStampedReferenceTest - change A->C false
原子更新基本类型
使用原子的方式更新基本类型,Atomic包提供了以下3个类。
AtomicBoolean:原子更新布尔类型。
AtomicInteger:原子更新整型。
AtomicLong:原子更新长整型。
/**
* @Author blackcat
* @create 2021/8/2 14:42
* @version: 1.0
* @description:AtomicInteger 相关方法
*/
@Slf4j
public class AtomicIntegerTest {
public static void main(String[] args) {
AtomicInteger i = new AtomicInteger(0);
// 获取并自增(i = 0, 结果 i = 1, 返回 0),类似于 i++
log.info("getAndIncrement:{}", i.getAndIncrement());
// 自增并获取(i = 1, 结果 i = 2, 返回 2),类似于 ++i
log.info("incrementAndGet:{}", i.incrementAndGet());
// 自减并获取(i = 2, 结果 i = 1, 返回 1),类似于 --i
log.info("decrementAndGet:{}", i.decrementAndGet());
// 获取并自减(i = 1, 结果 i = 0, 返回 1),类似于 i--
log.info("getAndDecrement:{}", i.getAndDecrement());
// 获取并加值(i = 0, 结果 i = 2, 返回 0)
log.info("getAndAdd:{}", i.getAndAdd(2));
// 加值并获取(i = 2, 结果 i = 0, 返回 0)
log.info("addAndGet:{}", i.addAndGet(-2));
// 获取并更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
log.info("getAndUpdate:{}", i.getAndUpdate(p -> p - 2));
// 更新并获取(i = -2, p 为 i 的当前值, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
log.info("updateAndGet:{}", i.updateAndGet(p -> p + 2));
// 获取并计算(i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
// getAndUpdate 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
// getAndAccumulate 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final
log.info("getAndAccumulate:{}", i.getAndAccumulate(10, (p, x) -> p + x));
// 计算并获取(i = 10, p 为 i 的当前值, x 为参数1, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
log.info("accumulateAndGet:{}", i.accumulateAndGet(-10, (p, x) -> p + x));
}
}
原子更新数组类型
通过原子的方式更新数组里的某个元素,Atomic包提供了以下的类。
AtomicIntegerArray:原子更新整型数组里的元素。
AtomicLongArray:原子更新长整型数组里的元素。
AtomicReferenceArray:原子更新引用类型数组里的元素。
/**
* @Author blackcat
* @create 2021/8/2 19:42
* @version: 1.0
* @description:
*/
@Slf4j
public class AtomicIntegerArrayTest {
static int[] value = new int[]{1, 2};
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
public static void main(String[] args) {
//以原子方式将数组位置i的元素设置成newValue值,则为3
ai.getAndSet(0, 3);
log.info("AtomicIntegerArray:{}", ai.get(0));
//如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值,重新设为1
boolean compareAndSet = ai.compareAndSet(0, 3, 1);
log.info("{}-------{}", compareAndSet, ai.get(0));
//int addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加。返回2
ai.getAndAdd(0, 1);
boolean addCompare = ai.compareAndSet(0, 3, 1);
log.info("add after------{}-------{}", addCompare, ai.get(0));
log.info("value;{}", value[0]);
}
}
需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组。
原子更新引用类型
AtomicReference:原子更新引用类型。
AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
AtomicMarkableReference:原子更新带有标记位的引用类型。
**
* @Author blackcat
* @create 2021/8/2 21:08
* @version: 1.0
* @description:
*/
@Slf4j
public class AtomicReferenceTest {
public static AtomicReference<User> atomicUserRef = new AtomicReference<User>();
public static void main(String[] args) {
User user = new User("小黑", 15);
atomicUserRef.set(user);
log.info("before:{}",atomicUserRef.get().toString());
User updateUser = new User("佩玲", 17);
atomicUserRef.compareAndSet(user, updateUser);
log.info("after:{}",atomicUserRef.get().toString());
}
static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
原子字段更新器
AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
AtomicLongFieldUpdater:原子更新长整型字段的更新器。
AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起
来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的
ABA问题。
利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常 Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type
/**
* @Author blackcat
* @create 2021/8/2 21:15
* @version: 1.0
* @description:
*/
@Slf4j
public class AtomicIntegerFieldUpdaterTest {
private volatile int field;
public static void main(String[] args) {
AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdaterTest.class, "field");
AtomicIntegerFieldUpdaterTest old = new AtomicIntegerFieldUpdaterTest();
fieldUpdater.compareAndSet(old, 0, 10);
// 修改成功 field = 10
System.out.println(old.field);
// 修改成功 field = 20
fieldUpdater.compareAndSet(old, 10, 20);
System.out.println(old.field);
// 修改失败 field = 20
fieldUpdater.compareAndSet(old, 10, 30);
System.out.println(old.field);
}
}
原子累加器
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;
/**
* @Author blackcat
* @create 2021/8/2 22:07
* @version: 1.0
* @description:累加器比较
*/
public class AccCompare {
private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
T adder = adderSupplier.get();
long start = System.nanoTime();
List<Thread> ts = new ArrayList<>(10);
// 4 个线程,每人累加 50 万
for (int i = 0; i < 4; i++) {
ts.add(new Thread(() -> {
for (int j = 0; j < 500000; j++) {
action.accept(adder);
}
}));
}
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);
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
demo(() -> new LongAdder(), adder -> adder.increment());
}
for (int i = 0; i < 5; i++) {
demo(() -> new AtomicLong(), adder -> adder.getAndIncrement());
}
}
}
20000000 cost:59
20000000 cost:46
20000000 cost:51
20000000 cost:60
20000000 cost:45
20000000 cost:340
20000000 cost:563
20000000 cost:519
20000000 cost:527
20000000 cost:523
AtomicLong是多个线程针对单个热点值value进行原子操作。而LongAdder是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作。
比如有三个ThreadA、ThreadB、ThreadC,每个线程对value增加10。
对于AtomicLong,最终结果的计算始终是下面这个形式 value= 10+10+10=30
但是对于LongAdder来说,内部有一个base变量,一个Cell[]数组。
base变量:非竞态条件下,直接累加到该变量上
Cell[]数组:竞态条件下,累加个各个线程自己的槽Cell[i]中
value = base + cell[0]+....+cell[i]
Unsafe
Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得。
/**
* @Author blackcat
* @create 2021/8/2 21:24
* @version: 1.0
* @description:获取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;
}
}
/**
* @Author blackcat
* @create 2021/8/2 21:25
* @version: 1.0
* @description:自己实现AtomicData
*/
@Slf4j
public class AtomicData {
private volatile int data;
static final Unsafe unsafe;
static final long DATA_OFFSET;
static {
unsafe = UnsafeAccessor.getUnsafe();
try {
// data 属性在 DataContainer 对象中的偏移量,用于 Unsafe 直接访问该属性
DATA_OFFSET = unsafe.objectFieldOffset(AtomicData.class.getDeclaredField("data"));
} catch (NoSuchFieldException e) {
throw new Error(e);
}
}
public AtomicData(int data) {
this.data = data;
}
public void decrease(int amount) {
int oldValue;
while(true) {
// 获取共享变量旧值,可以在这一行加入断点,修改 data 调试来加深理解
oldValue = data;
// cas 尝试修改 data 为 旧值 - amount,如果期间旧值被别的线程改了,返回 false
if (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue, oldValue - amount)) {
return;
}
}
}
public void increment(int amount) {
int oldValue;
while(true) {
oldValue = data;
if (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue, oldValue + amount)) {
return;
}
}
}
public int getData() {
return data;
}
public static void main(String[] args) throws InterruptedException {
AtomicData atomicData = new AtomicData(100);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
atomicData.decrease(1);
}
}, "add");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
atomicData.increment(1);
}
}, "sub");
t1.start();
t2.start();
t1.join();
t2.join();
log.info("result:{}", atomicData.getData());
}
}