基本原理
CAS基本原理(Compare And Swap) 利用了现代处理器都支持 CAS 指令,循环这个指令,直到成功为止;
什么是原子操作?如何实现原子操作?
原子操作
要么全部完成,要么全部都不完成的操作;
例如:synchronized 关键字包裹的方法或者代码块就称为原子操作,但是因为这个关键字太重了,所以 JDK 就提出了 CAS;
代码示例:
csharp
public void synchornized add() {
i++;
}
如果只为了实现一个 i++ 的操作,就是用 synchornized 的话,太重了,所以JDK 提出 CAS 操作;
如何实现原子操作
compare and swap 比较并且交换,包含了至少两步,现代 CPU 中,都提供了单独的指令(CAS指令),由 CPU 来保证这两个操作一定是原子操作,要么全部完成,要么全不完成;
原理解释:
假设有四个线程都要执行 count ++ 的操作,count 初始值为 0 ,首先这四个线程先将 count 值取到自己的内部,四个线程在自己的内部进行累加操作,也就是说把 count 由 0 变成了 1,接下来这四个线程都想把 count = 1 的值重新写回内存中去,那么四个线程同时写肯定是有问题的,所以 JDK 内部就使用了现代 CPU 所支持的这个 CAS 指令,首先只有一个线程能够执行这个指令(CPU保证),然后进行比较,比较内存中 count 的值是不是等于 0 ,因为当前这个线程在执行 count++ 的时候,是以 count = 0 为基准的,如果是 0,那么就把这个值 swap 成 1,执行完成之后退出,接着第二个线程进来,然后进行比较,比较内存中 count 的值是不是等于 0 ,因为这个线程在执行 count++ 的时候,也是以 count = 0 为基准的,但是此时 count 显然已经不是 0 了,已经被前一个线程给 swap 成 1 了,于是当前这个线程就会把 count = 1 再重新取一次,以 count = 1 进行 count++ 操作,然后进行比较,比较内存中 count 的值是不是等于 1,如果是 1,就把这个值 swap 成 2,依次类推,其他的线程也是执行这样的操作,直到这四个线程全部执行结束;
这里牵涉到了两个比较重要的概念
悲观锁 和 乐观锁
悲观锁:synchronized 关键字就是一个悲观锁,为什么?因为它在执行的时候总认为有人要改它的东西,那么它就先下手,先把这个锁抢到了,然后安安心心执行自己的操作;
乐观锁:在进行操作之前,没有其他人改我的东西,如果有人改了,那么我就重新来一次;
原子变量的操作性能要比锁的性能高;因为 synchornized 关键字在多个线程操作的时候只有一个线程能执行,其他线程就会被阻塞,一旦被阻塞了,就会发生上下文切换,而上下文的切换比较耗费性能(一次上下文的切换要耗费 3-5 毫秒);现代 CPU 在执行一条 CAS 的指令大概在 0.6ns,虽然可能一直在那里自旋操作,但是也比一次上下文切换耗费的时间要少,而且当前线程在一次拿锁释放锁要发生两次上下文切换,这个时间(3-5毫秒)是要 *2 的;而 CAS 中线程是不会进入这种阻塞状态的,它会不断的进行重试;
那么 CAS 既然这么强大,JDK 为什么没有完全采用 CAS?
CAS带来的问题
- ABA 问题
- 假设有两个线程,线程1 想把变量由 A 变成 B,线程1 在执行操作的过程中,结果 线程 2 比线程 1 要快一步的把 变量 由 A 变成 B 再快速变回了 A,当线程1 执行 swap 操作的时候,发现它还是 A,执行了 swap 操作;这就是 ABA 问题,如果不关心这个问题的话,问题也不大,也是CAS指令与生俱来的机制带来的问题;
- 开销问题
- 自旋操作,长期不成功,对于 CPU 来说开销还是比较大的;
- 只能保证一个共享变量的原子操作
- 原子操作针对的是内存中的一个变量,而不能同时操作三个变量的交换;
原子变量类
JDK 中所有以 Atomic 开头的类都被称为原子变量类;
- 更新基本类型类
-
AtomicBoolean
-
AtomicInteger
-
AtomicLong
- 更新数组类
-
AtomicIntegerArray
-
AtomicLongArray
-
AtomicReferenceArray
- 更新引用类型类
-
AtomicReference
-
用来解决 只能保证一个共享变量的原子操作 的问题
-
AtomicMarkableReference
-
带有版本戳的原子类,用来解决ABA的问题,只关心这个标记版本戳的变量有没有变过;
-
AtomicStampedReference
-
带有版本戳的原子类,用来解决ABA的问题,不但关心这个比较版本戳的变量有没有变过,还关心变动过几次;
基本使用
AtomicInteger的基本使用
typescript
public class UseAtomic {
static AtomicInteger atomicInteger = new AtomicInteger(10);
public static void main(String[] args) {
// 这两个就是 i++ 和 ++i 的操作
atomicInteger.getAndIncrement();
atomicInteger.incrementAndGet();
atomicInteger.addAndGet(24);
}
}
AtomicReference的基本使用
typescript
public class UseAtomicReference {
static AtomicReference<UserInfo> atomicReference;
public static void main(String[] args) {
UserInfo userInfo = new UserInfo("Kobe", 42);
atomicReference = new AtomicReference<>(userInfo);
UserInfo jameInfo = new UserInfo("James", 37);
atomicReference.compareAndSet(userInfo, jameInfo);
System.out.println(atomicReference.get());
}
static class UserInfo {
private volatile String name;
private int age;
public UserInfo(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "UserInfo{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
简历润色
简历上可写:深度理解Java多线程、线程安全、并发编程、CAS原理、可手写ThreadLocal核心实现;
下一章预告
带你玩转阻塞队列和线程池原理;