CAS和AQS知识理解

什么是CAS机制(compare and swap)

CAS算法的作用:解决多线程条件下使用锁造成性能损耗问题的算法,保证了原子性,这个原子操作是由CPU来完成的
CAS的原理:CAS算法有三个操作数,通过内存中的值(V)、预期原始值(A)、修改后的新值。
(1)如果内存中的值和预期原始值相等, 就将修改后的新值保存到内存中。
(2)如果内存中的值和预期原始值不相等,说明共享数据已经被修改,放弃已经所做的操作,然后重新执行刚才的操作,直到重试成功。
注意:
(1)预期原始值(A)是从偏移位置读取到三级缓存中让CPU处理的值,修改后的新值是预期原始值经CPU处理暂时存储在CPU的三级缓存中的值,而内存指定偏移位置中的原始值。
(2)比较从指定偏移位置读取到缓存的值与指定内存偏移位置的值是否相等,如果相等则修改指定内存偏移位置的值,这个操作是操作系统底层汇编的一个原子指令实现的,保证了原子性

JVM中CAS是通过UnSafe类来调用操作系统底层的CAS指令实现。CAS基于乐观锁思想来设计的,其不会引发阻塞,synchronize会导致阻塞。

缺点

1. ABA问题
ABA问题:CAS在操作的时候会检查变量的值是否被更改过,如果没有则更新值,但是带来一个问题,最开始的值是A,接着变成B,最后又变成了A。经过检查这个值确实没有修改过,因为最后的值还是A,但是实际上这个值确实已经被修改过了。为了解决这个问题,在每次进行操作的时候加上一个版本号,每次操作的就是两个值,一个版本号和某个值,A——>B——>A问题就变成了1A——>2B——>3A。在jdk中提供了AtomicStampedReference类解决ABA问题,用Pair这个内部类实现,包含两个属性,分别代表版本号和引用,在compareAndSet中先对当前引用进行检查,再对版本号标志进行检查,只有全部相等才更新值。

2. 可能会消耗较高的CPU
看起来CAS比锁的效率高,从阻塞机制变成了非阻塞机制,减少了线程之间等待的时间。每个方法不能绝对的比另一个好,在线程之间竞争程度大的时候,如果使用CAS,每次都有很多的线程在竞争,也就是说CAS机制不能更新成功。这种情况下CAS机制会一直重试,这样就会比较耗费CPU。因此可以看出,如果线程之间竞争程度小,使用CAS是一个很好的选择;但是如果竞争很大,使用锁可能是个更好的选择。在并发量非常高的环境中,如果仍然想通过原子类来更新的话,可以使用AtomicLong的替代类:LongAdder。

3. 不能保证代码块的原子性
Java中的CAS机制只能保证共享变量操作的原子性,而不能保证代码块的原子性。

优点

  • 可以保证变量操作的原子性;
  • 并发量不是很高的情况下,使用CAS机制比使用锁机制效率更高;
  • 在线程对共享资源占用时间较短的情况下,使用CAS机制效率也会较高。

AQS 是什么?为什么需要 AQS ?

试想有这么一种场景:有四个线程由于业务需求需要同时占用某资源,但该资源在同一个时刻只能被其中唯一线程所独占。那么此时应该如何标识该资源已经被独占,同时剩余无法获取该资源的线程又该何去何从呢?

这里就涉及到了关于共享资源的竞争与同步关系。对于不同的开发者来说,实现的思路可能会有不同。这时如果能够有一个较为通用的且性能较优同步框架,那么可以在一定程度上帮助开发人员快速有效的完成多线程资源同步竞争方面的编码。

AQS 正是为了解决这个问题而被设计出来的。AQS 是一个集同步状态管理、线程阻塞、线程释放及队列管理功能与一身的同步框架。其核心思想是当多个线程竞争资源时会将未成功竞争到资源的线程构造为 Node 节点放置到一个双向 FIFO 队列中。被放入到该队列中的线程会保持阻塞直至被前驱节点唤醒。值得注意的是该队列中只有队首节点有资格被唤醒竞争锁。

AQS是多线程同步器,它是JUC包中多个组件的底层实现,例如Lock,CountDownLatch,semaphore都用到了AQS。从本质上来说,AQS提供了两种锁的机制,分别是排他锁和共享锁。

排他锁:

存在多个线程去竞争同一个共享资源的时候,同一时刻只允许一个线程去访问这个共享资源,多个线程中只能有一个线程能够获得锁资源。例如lock中的reentrantlock重入锁的实现就是使用到了AQS中的排他锁功能

共享锁:

共享所也称为读锁,在同一个时刻允许多个线程同时获得锁资源,例如countdownlatch和semaphore都用到了AQS中的共享锁的功能

AQS作为互斥锁来说,他的整个设计体系中需要解决三个核心的问题:

(1)互斥变量的设计以及如何保证多线程同时更新互斥变量的时候线程的安全性

AQS采用了一个int类型的互斥变量‘state’,用来记录锁竞争的状态。0表示当前没有任何线程竞争到锁资源,大于等于1表示已经有线程正在持有锁资源。当一个线程想要获得锁资源的时候,首先需要判断‘state’是否等于0,是否处于无锁状态。若是,这把状态更新为1,表示占用锁。这个过程如果多个线程同时去操作,就会导致线程安全问题。

解决方案:AQS采用了CAS机制,CAS机制去保证‘state’互斥变量更新的原子性

(2)未竞争到锁资源的线程的等待以及竞争到锁的资源释放锁之后的唤醒

未获得到锁的线程通过unsafe类中的park方法去进行阻塞,把阻塞的线程按照先进先出的原则加入到一个双向链表的队列之中,当获得锁资源的线程释放锁之后,会从双向链表的头部去唤醒下一个等待的线程,再去竞争锁

(3)锁竞争的公平性和非公平性

在竞争锁资源的时候,公平锁需要去判断双向链表中是否有阻塞的线程,如果有则需要去排队等待。而非公平锁的处理方式是,不管想想链表中是否存在等待竞争锁的线程,他都会去直接尝试更改互斥变量‘state’去竞争锁。假设在一个临界点,获得锁的线程释放锁,此时state等于0,而当前的这个线程去抢占锁的时候正好可以把state修改成1,那么这时他就可以拿到锁,而这个过程是非公平的。

你可能感兴趣的:(java,java,开发语言)