整理笔记:多线程 并发 CAS问题
使用锁机制会遇到的问题?
- 在多线程竞争下,上锁和释放锁都会导致比较多的上下文切换和调度延时,从而影响性能。
- 一个线程持有锁会导致其他所有需要此锁的线程挂起。
- 如果高优先级的线程要等低优先级的线程释放后再执行,这样就引起优先级混乱,从而也影响性能。
- synchronized是独占锁,会导致其他所有需要锁的线程挂起,等待持有锁的线程释放锁。独占锁是一种悲观锁。解决这个问题需要用到乐观锁的概念。
所谓乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果遇到冲突造成失败就会去重试该操作,直到成功为止。
☞乐观锁的机制就是CAS
CAS
使用CAS的目的
乐观锁的机制就是CAS,当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,因为其他线程只会收到操作失败的信号。
因此,使用CAS就是利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其他原子操作都是利用类似的特性完成的。而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大提升。
CAS的三大操作数:V ,A,B
(1)V:内存地址V
(2)A:预期原值A
(3)B:新值B
操作流程:
先比较内存地址V处的值与预期原值A是否相等,如果相等就将内存地址V处更新为新值B。
在配合循环使用时,若CAS操作失败,会循环执行或到达某个终止处。此操作配合循环使用时。又称为“自旋锁”的实现方式。
使用CAS存在的问题?
(1)ABA问题:
线程操作时,先将内存的V中的A值改成B值,然后又将值B更新为值A,导致最终CAS判断内存V处的值并没有发生变化,认为更新没有成功,但是实际上已经更新过了。
一个形象的比喻帮助理解:
曾经看到过有个老师这么比喻,感觉挺有意思的,好像说的也挺那么回事:程序员二币有个很漂亮的女朋友阿绿,有一天二币被通知要出差一个月,这一个月期间阿绿和他们隔壁老王勾搭上了,并且发生了不可描述的事情,一个月后二币出差回来了,他看到自己的女朋友阿绿依旧没有任何变化依旧很漂亮,但是其实阿绿已经不是阿绿已经被老王更新迭代过了。
解决ABA问题常见的办法:
- 加时间戳。
- 加版本号
(2)循环时间长开销大:
不断的循环重试,如果线程较多,资源抢占激烈,而成功率较低的情况下,循环越多消耗的资源就越多。
解决这种问题:常见的是设置最大次数,如果循环至最大次数仍没有成功则自动放弃,避免无限循环。
(3)只能保证一个共享变量的原子操作:
CAS最多只能操作一个共享变量,而单体的效率低。