- CAS的全称是compare and swap(比较并且交换),是解决多线程安全的一种方法;是一种自旋锁也是乐观锁,而synchronized锁是一种悲观锁。
- CAS其实是计算机的指令,格式为:(变量内存地址,变量旧值,变量新值)。
CAS cpu层理解
底层硬件通过将 CAS 里的多个操作在硬件层面语义实现上,通过一条处理器指令保证了原子性操作。这些指令如下所示:
(1)测试并设置(Tetst-and-Set)
(2)获取并增加(Fetch-and-Increment)
(3)交换(Swap)
(4)比较并交换(Compare-and-Swap)
(5)加载链接/条件存储(Load-Linked/Store-Conditional)
前面三条大部分处理器已经实现,后面的两条是现代处理器当中新增加的。而且根据不同的体系结构,指令存在着明显差异。
在IA64,x86 指令集中有 cmpxchg 指令完成 CAS 功能,在 sparc-TSO 也有 casa 指令实现,而在 ARM 和 PowerPC 架构下,则需要使用一对 ldrex/strex 指令来完成 LL/SC 的功能。在精简指令集的体系架构中,则通常是靠一对儿指令,如:load and reserve 和 **store conditional ** 实现的,在大多数处理器上 CAS 都是个非常轻量级的操作,这也是其优势所在。
CAS问题:
- ABA问题
线程一:A->B->A
线程二:A- - - >C
线程一和线程二同时读取到了变量A到各自的私有内存中,线程一运行的比较快,将变量A变为B后,又变为了A;此时线程二才执行到CAS指令,根据自己保存的变量A地址去内存查看是不是还是A,这个时候发现是A,没有感觉到B存在过,就更新为了C。 - 开销问题
自旋CAS如果长时间操作不成功,会给CPU带来非常大的执行开销。
3.只能保证一个共享变量的操作
解决:
主要是ABA问题:
线程一:A1 ->B1 ->A2
线程二:A1-> -> 发现变量A值不是A1,重新读取。
增加版本戳,每次修改变量后,同步更新变量的版本号。
可以使用AtomicMarkableReference ->返回变量是否变动过
AtomicStampedReference ->返回变量变动过几次
原子操作类
基本使用:
AtomicInteger ai = new AtomicInteger(10);
ai.getAndIncrement(); – 返回原先的值10
ai.incrementAndGet(); --返回增加后的值11
思考 synchronize为什么比CAS要慢?
synchronize使用后,其他线程就处于阻塞状态,而要唤起,需要进行上下文的切换。而且synchronize使用要进行加锁、释放锁等操作,指令执行时间长。
LOCK显示锁
lock锁为什么比synchronize锁慢?
synchronize锁从jdk1.5之后一直在不断的优化,效率一直在提升,而且是java自带的内置锁。但是lock锁只是一个接口,使用的时候需要实例化,所以一般在没有尝试获取锁、中断锁这些需求的场景下,一般使用synchronize锁。
公平锁与非公平锁
非公平锁理解:
一个在使用锁的线程执行完后,其他阻塞的线程要进行唤醒,此时要进行上下文切换。而如果此时刚好有一个线程处于等待状态,不用进入阻塞队列里,直接获取到锁使用,就是非公平锁。
非公平锁效率高原因分析
一个锁被使用的时候,其他线程要处于阻塞状态,而如果要将这些阻塞的线程唤醒就意味着要进行上下文的切换,在计算机中上下文切换是很耗时的操作;
假如:一个线程使用完锁了,此时操作系统去唤醒阻塞线程去了,这个时候锁处于空着状态,而同时有另一个新线程进来了,也要获取这个锁,如果是非公平锁的话,就直接给这个线程使用了,这个线程就不用进入这个锁的阻塞队列等待了。如果新线程执行完了,阻塞线程也刚好上下文切换完成,这样就大大提高了效率。
synchronized是非公平锁;
ReentraLock默认也是非公平锁。
公平锁用处
既然非公平锁效率高为什么还有公平锁呢?那是因为在非公平锁的情况下,可能会出现线程饥饿的情况,所以为了保证所有线程都能被执行,会用到公平锁。
推荐个很好的博客来理解CAS
https://www.cnblogs.com/ldws/p/11970087.html