悲观锁
悲观锁的含义
总是假设是最坏的情况,每次去拿数据的时候都会认为会被别人改变,所以每次操作时都会加锁,这样别人拿这个数据就会阻塞直到它拿到锁。
在Java中的具体实现就是**synchronized****,**它就是悲观锁
悲观锁存在的问题
在多线程竞争下,加锁,释放锁会导致悲观锁锁的上下文切换和调度延时引起的性能问题,一个线程持有锁会导致其他线程进入阻塞状态。
乐观锁CAS
乐观锁认为数据一般不会产生冲突,所以在数据进行提交更新时,才会真正对数据是否产生冲突,如果产生冲突,返回给用户错误信息,由用户决定如何做。
CAS:compare and swap 比较并交换
先获取一次对象,在第二次继续获取如果相同则获取锁。
CAS:比较并修改
这里我们重点研究一下CAS
含义
CAS是乐观锁的技术实现,当多个线程尝试使用CAS同时来更新同一个变量,只有一个线程能够更新的变量值,而其他线程都会失败,失败的线程并不会被挂起,会被告知这次竞争失败,可以再次尝试。
CAS操作包含三个操作数
- 需要读写的内存位置 V
- 需要比较的预期原值 A
- 拟写入的新值
这里要强调一句:乐观锁是一种思想,CAS是CAS的一种实现方法
Java中对CAS支持,在jdk1.5之后新增Java**.util.concurrent(J.U.C)就是建立CAS基础上,CAS是一种非阻塞的实现,例如:Atomic**
CAS存在的问题
CAS中的ABA问题
CAS使用起来可以提高性能,但是会引起ABA问题
什么叫ABA问题呢?
假如如下事件序列:
1、线程1从内次位置V来获取值A
2、线程2从内存位置V获取A
3、线程2进行一些操作,将B写入到V
4、线程2将A写入位置V
5、线程1进行CAS操作,发现位置V的值任然为A,操作成功了
6、线程1尽管CAS操作成功了,该过程有可能出现问题,对于线程1,线程2做的处理就可能丢失了。
举一个例子:
举例说明:一个链表ABA的例子
1、现有一个用单向链表实现的堆栈,栈顶为A。这时线程T1已经知道A.next为B,然后希望用CAS将栈顶替换为B:
1head.compareAndSet(A,B);
2、在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再依次入栈D、C、A,而对象B此时处于游离状态。
3、此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B。但实际上B.next为null,此时堆栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,C、D被丢掉了。
那么怎么解决呢?
解决思路就是使用版本号,在变量前面追加版本号,每次对变量进行更新的时候对版本进行+1,对于A-B-A就会变为 1A-2B-3A
循环时间长开销大
自旋CAS如果长时间不成功,CPU会带来非常大的执行开销,需要考虑长时间循环的问题。
**解决:**给每个线程循环给定循环次数阈值,让当前线程释放CPU的使用权,进入阻塞中
只能保证一个共享变量的原子操作
非常大的执行开销,需要考虑长时间循环的问题。
**解决:**给每个线程循环给定循环次数阈值,让当前线程释放CPU的使用权,进入阻塞中
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。