基本原理
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核心实现;