,而在我看来我是不知道他问的是那个CAS 我一般会问面试官,问他问的CAS是"原子操作",还是"单点登录" 因为在JAVA并发中的原子操作是称为CAS的,也就是英文单词CompareAndSwap CAS(Compare And Swap): 我们先要学习的是并发编程中的CAS,也就是原子操作 那么,什么是原子操作?如何实现原子操作? CAS以一种乐观锁的方式实现并发控制 如何实现原子操作: Java可以通过锁和循环CAS的方式实现原子操作 为什么要有CAS: CAS就是比较并且替换的一个原子操作,在CPU的指令级别上进行保证 只能保证一个共享变量的原子操作 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法 比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。
CAS 我一般会问面试官,问他问的CAS是"原子操作",还是"单点登录" 因为在JAVA并发中的原子操作是称为CAS的,也就是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。 CAS(Compare And Swap): 我们先要学习的是并发编程中的CAS,也就是原子操作 那么,什么是原子操作?如何实现原子操作? CAS以一种乐观锁的方式实现并发控制 如何实现原子操作: Java可以通过锁和循环CAS的方式实现原子操作 为什么要有CAS: CAS就是比较并且替换的一个原子操作,在CPU的指令级别上进行保证 只能保证一个共享变量的原子操作 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法 比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。
CAS是CPU指令,CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,那么就可以再次尝试 Go中使用CAS通常是结合for 无限循环 来实现原子化更新操作:for { // 假设data为共享变量,同一时刻可能有多个线程会更新它 old := data ok := atomic.CompareAndSwapInt64 (&data, old, new) if ok { return new }}CompareAndSwap会先进行比较,如果data的值等于old,那么就会执行替换操作并返回 true,如果不等于,则说明已经被其他线程操作了就返回false,所以它并不一定总能成功,尤其是在并发大的情况下,所以使用for循环来自旋。 造成很大的开销 一次只能保证一个共享变量的原子操作ABA问题-变量曾经被改过,CAS无法感知,可以通过变量版本解决
AtomicInteger实现CAS 使用AtomicInteger、AtomicBoolean等原子操作类可以完成原子操作。 也就是说,在每个用到CAS操作的整形值的地方,都需要维护一个AtomicInteger对象,也就是占用4+8=12字节。 只需要先分配一个静态变量,然后在每个用到CAS操作的整形值的地方,都只需要维护一个volatile int变量,也就是占用4字节。 而这两个函数是基于refCntUpdater对refCnt的CAS操作。 (在这瞬间,线程切换) 线程2用cas把x设置为B 线程2用cas把x设置为A (线程切换回来)线程1查询到x的值为A,于是cas理所当然地把x改为了C。
介绍 CAS技术是为了解决问题而生的,通过 CAS 我们可以以无锁的方式,保证对共享数据进行 “读取 - 修改 - 写回” 操作序列的正确性。CAS 是乐观锁设计思想的实现。 在大多数处理器上 CAS 都是非常轻量级的操作,这也是其优势所在。Java 的 CAS 操作CAS 依赖于 Unsafe 类提供的一些底层能力,进行底层操作。 目前 Java 提供了两种公共 API,可以实现 CAS 操作:一种是 Atomic 原子类。Atomic 包中的类对 Unsafe 类进行了封装,使我们可以更方便的使用 CAS 操作。 还有一种是 Variable Handle API,它源自于JEP 193,提供了各种粒度的原子或者有序性的操作等。CAS 的优劣局限CAS 的优点:在大多数处理器上 CAS 都是非常轻量级的操作。 如果有大量的线程同时对一个共享变量进行 CAS 操作,竞争过于激烈的情况下,尝试进行 CAS 操作的线程只会白白消耗处理器资源,而不会做任何有价值的工作,这就会带来性能的浪费。
CAS 操作包含三个操作数:内存位置(V)、预期值(A)、新值(B)。如果内存位置的值(V)与预期原值(A)相同,处理器会将该位置的值更新为新值(B)则 CAS 操作成功。 Unsafe 类存在于 sun.misc 包中,其内部方法操作可以像 C的指针一样直接操作内存,因为 Java 中 CAS 的操作依赖于 Unsafe 类的方法。 【2】**只能保证一个共享变量的原子操作:**只能保证一个共享变量的原子操作。 当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作 比如有两个共享变量 i=2,j=a,合并一下 ij=2a,然后用CAS 来操作 ij。
CAS实现原子性操作 CAS操作大概有如下几步: 读取旧值为一个临时变量 对旧值的临时变量进行操作或者依赖旧值临时变量进行一些操作 判断旧值临时变量是不是等于旧值,等于则没被修改,那么新值写入.不等于则被修改 那么步骤三实际上就是比较并替换,这个操作需要是原子性的,不然无法保证比较操作之后还没写入之前有其他线程操作修改了旧值.那么这一步实际上就是CAS(CompareAndSwap),其是需要操作系统底层支持 = 高效并发,那么CAS就是用来实现这个操作的原子性. CAS与乐观锁是什么关系? 乐观锁是一种思想,其认为冲突很少发生,因此只在最后写操作的时候加锁,这里的加锁不一定是真的锁上,比如CAS一般就用来实现这一层加锁. 在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
「CAS」 操作有3个原子性操作: 读取内存的值 将内存的值与期望值比较 如果相等,则将内存值更新为新值 这三个操作一起完成,中间不会被线程切换打断。这就保证了比较和交换的原子性。 该方法尝试使用「CAS」操作更新obj的值,当且仅当obj的值等于expected时才更新,否则不做任何操作。 示例 C# 中提供了 Interlocked 类来实现 「CAS」 操作。 1); // val 保持 1, 返回 1 Interlocked.Increment(ref val); // val = 2, 返回 2 Interlocked.Decrement 当 「CAS」 失败时,会进行重试,消耗 CPU 资源。 只能在某些平台使用。需要硬件对 「CAS」 操作的支持,一些低端硬件并不支持 「CAS」。 一般来说,当操作一个共享变量时使用 「CAS」,操作多个共享变量时使用锁可能更高效。如果硬件不支持 「CAS」,也只能使用锁。
于是JDK提供了一系列原子操作类:AtomicInteger、AtomicLong、AtomicBoolean、AtomicReference等,它们都是基于CAS去实现的,下面我们就来详细看一看原子操作类 CAS技术就是乐观锁的一种形式,Compare And Swap顾名思义比较交换,它会比较操作之前的值和预期的值是否一致,一致才进行操作,否则什么都不做,然后循环去CAS。 public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); 简单CAS操作的弊端 我们可以设想一个场景:你要向银行卡中存入1000元钱,在存之前有2000,存之后应该是3000元。 带时间戳的CAS操作类AtomicStampedeReference 为了解决这种问题,JDK提供了一个带有时间戳的CAS操作类AtomicStampedeReference,它内部不仅维护了对象的值,
本节我们先来看看go中CAS操作 二、CAS操作 go中的Cas操作与java中类似,都是借用了CPU提供的原子性指令来实现。 CAS操作修改共享变量时候不需要对共享变量加锁,而是通过类似乐观锁的方式进行检查,本质还是不断的占用CPU 资源换取加锁带来的开销(比如上下文切换开销)。 五个信号量 wg.Add(threadNum) //2.开启5个线程 for i := 0; i < threadNum; i++ { go incCounter 这里之所以使用无限循环是因为在高并发下每个线程执行CAS并不是每次都成功,失败了的线程需要重写获取变量当前的值,然后重新执行CAS操作。 三、总结 go中CAS操作具有原子性,在解决多线程操作共享变量安全上可以有效的减少使用锁所带来的开销,但是这是使用cpu资源做交换的。
本节我们先来看看go中CAS操作 二、CAS操作 go中的Cas操作与java中类似,都是借用了CPU提供的原子性指令来实现。 CAS操作修改共享变量时候不需要对共享变量加锁,而是通过类似乐观锁的方式进行检查,本质还是不断的占用CPU 资源换取加锁带来的开销(比如上下文切换开销)。 五个信号量 wg.Add(threadNum) //2.开启5个线程 for i := 0; i < threadNum; i++ { go incCounter 这里之所以使用无限循环是因为在高并发下每个线程执行CAS并不是每次都成功,失败了的线程需要重写获取变量当前的值,然后重新执行CAS操作。 三、总结 go中CAS操作可以有效的减少使用锁所带来的开销,但是这是使用cpu资源做交换的。
CAS与OAuth2的区别 一、 CAS的单点登录时保障客户端的用户资源的安全 。 OAuth2则是保障服务端的用户资源的安全 。 OAuth2获取的最终信息是,我(oauth2服务提供方)的用户的资源到底能不能让你(oauth2的客户端)访问。 三、 CAS的单点登录,资源都在客户端这边,不在CAS的服务器那一方。 用户在给CAS服务端提供了用户名密码后,作为CAS客户端并不知道这件事。 OAuth2认证,资源都在OAuth2服务提供者那一方,客户端是想索取用户的资源。 总结:所以cas登录和OAuth2在流程上的最大区别就是,通过ST或者code去认证的时候,需不需要预先商量好的密码。
Apereo CAS 通过使用bridge模式来支持多个协议:CAS、SAML2、OAuth2、OpenID Connect等。 CAS可部署软件包中已经包含了可以使用SAML2、OAuth2等协议的plugin/bridges/modules,这些plugins模块都是和CAS通信。 flow with an OAuth2-enabled client application: The CAS deployment has turned on the OAuth2 plugin. 添加依赖库 implementation "org.apereo.cas:cas-server-support-oauth-webflow" 2. 通过CAS Management UI也可以看到刚刚添加的‘OAuth2DemoClient’:
java 内存模型与 volatile 的实现 synchronized 的使用及实现原理 本文,我们来介绍保证并发安全的重要思想 — CAS。 2. CAS (Compare And Swap) CAS (Compare And Swap)是并发系统中,实现原子操作和锁的常见思想。 java 中,sun.misc.Unsafe 类提供了硬件级别的原子操作来实现 CAS,java.util.concurrent 包下的大量类都使用了这个 Unsafe.java 类的CAS操作。 (this, valueOffset, -1); } //当前值增加delta,返回旧值,底层CAS操作 public final int getAndAdd(int delta - 1; } //当前值增加delta,返回新值,底层CAS操作 public final int addAndGet(int delta) { return
原子操作与CAS 3.1 原子操作 所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何context switch,也就是切换到另一个线程。 但是synchronized关键字有一些很显著的问题: 1、synchronized是基于阻塞锁的机制,如果被阻塞的线程优先级很高,可能很长时间其他线程都没有机会运行; 2、拿到锁的线程一直不释放锁, JAVA内部在实现原子操作的类时都应用到了CAS。 3.2 CAS CAS是CompareAndSwap的缩写,即比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。 如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。 只能保证一个共享变量的原子操作 只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性
图2-13 乐观锁 根据从上面的概念描述我们可以发现: l 悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。 l 乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。 2.5.3 CAS在原子操作对象中的应用 下面的代码主要是使用了20个线程进行自增10000次来证明原子性。如代码2-9所示。 2.5.6 CAS与单例模式 用CAS也可以完成单例模式,虽然在正常开发中,不会有人用CAS来完成单例模式,但是是检验是否学会CAS的一个很好的题目。例代码2-14。 实际上虚拟机采用CAS配合上失败重试的方式保证更新操作的原子性,原理和上面讲的一样。 2. TLAB 。 只有当缓冲区的内存用光需要重新分配内存的时候才会进行CAS操作分配更大的内存空间。
通俗的理解就是CAS操作需要我们提供一个期望值,当期望值与当前线程的变量值相同时,说明还没线程修改该值,当前线程可以进行修改,也就是执行CAS操作,但如果期望值与当前线程不符,则说明该值已被其他线程修改 1535524330707.png 由于CAS操作属于乐观派,它总认为自己可以成功完成操作,当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败 基于这样的原理,CAS操作即使没有锁,同样知道其他线程对共享资源操作影响,并执行相应的处理措施。 1.2、CPU指令对CAS的支持 或许我们可能会有这样的疑问,假设存在多个线程执行CAS操作并且CAS的步骤很多,有没有可能在判断V和E相同后,正要赋值时,切换了线程,更改了值。造成了数据不一致呢? 答案是否定的,因为CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令
1 CAS原理 CAS是所有原子类的底层原理,乐观锁主要采用CAS算法。 CAS,比较并交换,是JDK提供的非阻塞原子性操作,通过硬件保证比较-更新操作的原子性。 CAS操作利用CPU的特殊指令,由CPU保证原子性,完成一系列操作,不存在安全性问题。 CAS的变量需要用volatile修饰,以便在各线程之间保证可见。 CAS算法思想的使用场景 乐观锁 并发容器,例如ConcurrentHashMap 原子类 2 AtomicLong中CAS使用分析 // 获取Unsafe实例 private static final CAS操作 } while(! 此处可能存在这样的情况,线程1获取变量值为5,线程2将值改为10,线程3再将值改回5。对于线程1,变量的值没有变,但对于计数等后续操作是不正确的。
如上图所示,Unsafe 提供的 105 个 API 大致可分为内存操作、CAS、Class 相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等。 CAS 操作主要涉及到下面 3 个 API。 ? CAS 即比较并替换,实现并发算法时常用到的一种技术。CAS 操作包含三个操作数——内存位置、预期原值及新值。 执行 CAS 操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。 说完 CAS,我们再来说说 Unsafe 的内存操作。 内存操作主要有下面 9 个 API。 ? 以上就是 Unsafe 类 8 大主要功能的 2 个重要的功能。其他功能,我会在对应使用到的框架脑图中串起来讲。当然,如果你们现在希望了解的话,我也可以提前写一下这方便的内容。
而在Doug Lea提供的cucurenct包中,CAS理论是它实现整个java包的基石。 CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。) 类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时 修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法 可以对该操作重新计算。 ,因为Java中CAS操作的助兴依赖于UNSafe类的方法。 3.3、不能保证多个共享变量的原子操作 问题描述:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性