CAS全称CompareAndSet或者CompareAndSwap,其中【比较-交换】的操作是原子的,下面先一个具体的案例
定义一个接口
interface Acount {
void withdraw(int amount);
Integer getBalance();
//设置余额为10000,一千个线程每个线程减少10,正确结果应该为0
static void demo(Acount acount) {
List<Thread> threadList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
threadList.add(new Thread(() -> acount.withdraw(10)));
}
long start = System.currentTimeMillis();
threadList.forEach(Thread::start);
threadList.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.currentTimeMillis();
System.out.printf("执行时间:%d,余额:%d", end - start, acount.getBalance());
}
}
不安全的实现
class AcountUnsafe implements Acount {
private Integer balance;
public AcountUnsafe(Integer balance) {
this.balance = balance;
}
@Override
public void withdraw(int amount) {
this.balance -= amount;
}
public Integer getBalance() {
return balance;
}
public void setBalance(Integer balance) {
this.balance = balance;
}
}
测试
public class Test1 {
public static void main(String[] args) {
Acount acount = new AcountUnsafe(10000);
Acount.demo(acount);
}
}
结果:

给withdraw加锁即可
@Override
public void withdraw(int amount) {
synchronized(this){
this.balance -= amount;
}
}
结果:

无锁采用原子整数AtomicInteger来实现
class AcountSafe implements Acount {
private AtomicInteger balance;
public AcountSafe(AtomicInteger balance) {
this.balance = balance;
}
@Override
public void withdraw(int amount) {
while (true) {
int prev = this.balance.get();
int value = prev - amount;
// compareAndSet是JVM提供的本地方法,该方法具有原子性
if (this.balance.compareAndSet(prev, value)) {
break;
}
}
}
public Integer getBalance() {
return balance.get();
}
}
结果

比较执行时间发现,无锁比有锁实现要快一点
先分析下上述代码的时序图

CAS底层使用的lock compxchg指令(X86架构),在单核CPU和多核CPU下都能保证【比较-交换】的原子性。CAS必须借助volatile才能读到共享变量的最新值,从而完成【比较-交换】的效果,来看看AtomicInteger的源码,其中初始值value就是用volatile修饰的

注意,volatile只能保证可见性,让其他线程看到最新值,但不能保证原子性即不能解决多个线程的指令交错问题。
CAS的作用是用无锁来实现多线程对共享变量操作的安全性,由于需要不停重试它也不是一定能提高效率。
CAS的效率:
CAS的特点:
AtomicInteger、AtomicBoolean、AtomicLong,这三个比较类似,都可以看做是对一个整数的封装。
public class AtomicIntegerTest {
public static void main(String[] args) {
AtomicInteger i = new AtomicInteger(0);
//自增等价于++i
System.out.println(i.incrementAndGet());
//i++
System.out.println(i.getAndIncrement());
System.out.println(i.get());
//类似的还有--i,i--
//增加指定的数
System.out.println(i.addAndGet(3));
System.out.println(i.getAndAdd(3));
System.out.println(i.get());
//其他计算,乘法等复杂运算
System.out.println(i.updateAndGet(x -> x * 2 - 1));
System.out.println(i.getAndUpdate(x -> x * 2 - 1));
System.out.println(i.get());
//自己实现updateAndGet
System.out.println(updateAndGet(i,x->(x-1)*2));
}
private static AtomicInteger updateAndGet(AtomicInteger i, IntUnaryOperator intUnaryOperator) {
int prev;
do {
prev = i.get();
} while (!i.compareAndSet(prev, intUnaryOperator.applyAsInt(prev)));
return i;
}
}
public class MyAtomicIntegerTest implements Acount {
public static void main(String[] args) {
Acount test = new MyAtomicIntegerTest(10000);
Acount.demo(test);
}
MyAtomicInteger balance;
public MyAtomicIntegerTest(int i) {
this.balance = new MyAtomicInteger(i);
}
@Override
public void withdraw(int amount) {
balance.incrementAndGet(-amount);
}
@Override
public Integer getBalance() {
return balance.get();
}
}
class MyAtomicInteger {
volatile int value;
private static Unsafe UNSAFE;
private static long valueOffset;
static {
try {
//利用反射获取Unsafe的实例
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
UNSAFE = (Unsafe) field.get(null);
//获取字段value相对类对象内存开始位置的偏移量
valueOffset = UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public MyAtomicInteger(int value) {
this.value = value;
}
public int get() {
return value;
}
public void incrementAndGet(int i) {
while (true) {
if (UNSAFE.compareAndSwapInt(this, valueOffset, this.get(), this.get() + i)) {
break;
}
}
}
}
测试结果:

使用AtomicReference实现Account
public class AtomicReferenceTest {
public static void main(String[] args) {
Acount acount = new AccountBigDecimalSafe(new BigDecimal("10000"));
Acount.demo(acount);
}
}
class AccountBigDecimalSafe implements Acount {
private AtomicReference<BigDecimal> balance;
AccountBigDecimalSafe(BigDecimal balance) {
this.balance = new AtomicReference<>(balance);
}
@Override
public void withdraw(int amount) {
while (true) {
BigDecimal prev = balance.get();
BigDecimal next = prev.subtract(new BigDecimal(amount));
if (balance.compareAndSet(prev, next)) {
break;
}
}
}
@Override
public Integer getBalance() {
return balance.get().intValue();
}
}
字符串A,先改成了B又改回A,主线程发现字符串还是A,就改成了C。一般ABA问题不会有什么影响,但在实际工作中还是注意。
public class AbaTest {
public static void main(String[] args) {
AtomicReference atomicReference = new AtomicReference("A");
System.out.println(atomicReference);
new Thread(() -> {
if (atomicReference.compareAndSet("A", "B")) {
System.out.println(atomicReference);
}
if (atomicReference.compareAndSet("B", "A")) {
System.out.println(atomicReference);
}
}).start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (atomicReference.compareAndSet("A", "C")) {
System.out.println(atomicReference);
}
}
}
可以使用AtomicMarkableReference解决ABA问题
public class AbaTest2 {
public static void main(String[] args) {
AtomicMarkableReference<String> atomicReference = new AtomicMarkableReference("A",false);
System.out.println(atomicReference.getReference());
new Thread(() -> {
if (atomicReference.compareAndSet("A", "B",false,true)) {
System.out.println(atomicReference.getReference());
}
if (atomicReference.compareAndSet("B", "A",false,true)) {
System.out.println(atomicReference.getReference());
}
}).start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (atomicReference.compareAndSet("A", "C",false,true)) {
System.out.println(atomicReference.getReference());
}
}
}
AtomicMarkableReference增加了是否改动过的标记,只要改动过,并被标记上,后面的改动就无法生效。
原子累加器,指的是累加操作是原子的,java8之前只能使用原子整数类的incrementAndGet方法,java8新增了LongAdder(并发大神狗哥,Doug Lea编写),专门用于累加操作,效率提升5倍。
public class LongAddrTest {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
addr(() -> new AtomicLong(0), (AtomicLong::incrementAndGet));
}
for (int i = 0; i < 5; i++) {
addr(LongAdder::new, LongAdder::increment);
}
}
static <T> void addr(Supplier<T> supplier, Consumer<T> action) {
T t = supplier.get();
List<Thread> threads = new ArrayList<>(4);
long start = System.currentTimeMillis();
for (int i = 0; i < 4; i++) {
threads.add(new Thread(() -> {
for (int j = 0; j < 500000; j++) {
action.accept(t);
}
}));
}
threads.forEach(Thread::start);
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));
}
}
测试结果:

LongAddr性能提升的原因是拆分了多个累加单元[cell0]…,当竞争激烈的时候,累加分散到多个cell减少失败重试,最后将结果汇总,最终减少竞争提高效率。