一、CAS
1、什么是CAS
首先,CAS是一种算法,不是锁
CAS,其实是个简称,全称是 Compare And Swap,对比之后交换数据
- CAS的实现原理
- 首先呢,我们通过它的全称就可以看出来,CAS先是对比数据,然后再执行具体的原子操作。
- 几个参数介绍:
- this:Unsafe 对象本身,需要通过这个类来获取 value 的内存偏移地址
- valueOffset:value 变量的内存偏移地址
- 这个就是现在要获取更新的那个变量
由此可见,他获取的这个value是通过volatile来修饰的,所以说直接从主存中获取这个值 - volatile的作用:
- 保障了共享变量的可见性(我们要了解JMM,即java内存模型,这个特点的实现靠的是 内存一致性协议)
- 防止指令重排序(单例模式的一种双检索实现里面(DCL)应用的就是这个特性)
- 这个就是现在要获取更新的那个变量
- expect:期望更新的值(一开始获取的值)
- update:要更新的最新值
- 如果原子变量中的 value 值等于 expect,则使用 update 值更新该值并返回 true,否则返回 false。
通俗来讲就是比如有一个值 int x = 0;要对x进行++操作,那么我就获取这个x的值,作为expect,进行++操作以后,x 等于1,往回更新的时候看看此时的x是不是还是0,如果还是原来的值,就更新x为1,如果此时x的值不是0,而是100,那么就是CAS失败,返回false
2、CAS进行原子操作的三大问题
- ABA问题
- 因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
- 解决办法:使用版本号来进行控制。在变量前面 追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A
- 循环时间长开销大
- 这种情况就是在循环CAS的情况产生的一种问题。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
- 解决办法就是可以给他设置一个上限值,超过这个值还没成功就返回false。
- 只能保证一个共享变量的原子操作
- 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性。
- 这个时候就可以用锁
- 是把多个共享变量合并成一个共享变量来 操作
3、循环CAS
应用场景最多,他的意思就是我在执行一次CAS操作下,看看是否可以执行成功,如果执行不成功,那么我就可以获取最新的值,重复进行操作,直到成功为止。
二、AQS
1、核心思想
AQS全称为AbstractQueuedSynchronizer,它的核心思想就是线程请求一个共享资源,如果这个资源目前没有被锁定,是处于空闲状态,那么这个线程就占用这块资源,并将这块共享资源的状态设置为锁定状态。如果这块资源被占用,已经处于锁定状态,那么这个线程就会被分配到一个队列里面,等待被唤醒获取这个锁(AQS通过CLH队列锁来实现,这个队列是一个FIFO队列,即先进先出)。
2、具体实现
- AQS使用一个int成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作
private volatile int state;//共享变量,使用volatile修饰保证线程可见性
compareAndSetState(int expect, int update)
通过CAS操作将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
3、AQS 对资源的两种共享方式
-
独占
- 只有一个线程能执行,典型应用就是ReentrantLock(重入锁)
- 分为公平锁和非公平锁
- 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
- 符合线程请求的绝对时间顺序
- 非公平锁:当线程获取锁的时候,先通过CAS操作去抢锁,如果没抢到再加入到队列中等待被唤醒。
- 可能造成线程“饥饿”,但极少的线程切换,保证了其更大的吞吐量,性能更高。但是非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。
- ReentrantLock
- ReentrantLock 默认采用非公平锁,因为考虑获得更好的性能,通过 boolean 来决定是否用公平锁(传入 true 用公平锁)
- 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
- 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。
- 如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。
- 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
-
共享
- 就是多个线程可同时执行
- 读写锁中的读锁
- CountDownLatch(倒计时器)
4、AQS 的应用
- ReentrantLock (上面已经讲解过了)
- ReentrantReadWriteLock(读写锁)
- 可以保证多个线程可以同时读,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。所以在读操作远大于写操作的时候,读写锁就非常有用了。
- Semaphore(信号量)
- CountDownLatch (倒计时器)
- CyclicBarrier(循环栅栏)