通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其Java实现! - 知乎 (zhihu.com)
锁分类
- 悲观锁,乐观锁
- 可重入锁
- 可中断锁
- 公平锁,非公平锁
- 读锁(共享锁),写锁(排他锁/互斥锁)
- 自旋锁
一、悲观锁与乐观锁
悲观锁:认为数据随时都可能更改,操作之前先加锁,别的人都不能获取此数据。
乐观锁:认为数据一般不会被更改,操作的时候会检查数据是否更新,发现数据变了,重新读取重新进行操作。
悲观锁阻塞事务,乐观锁回滚重试
二、乐观锁基础——CAS
CAS,就是比较并替换,Compare And Swap 。
每一个CAS操作过程都包含3个运算参数:一个内存地址V(当前值),一个期望的值A(旧值)和一个新值B。
CAS的基本思路就是,如果内存地址V上的值和期望的值A相等,则给其赋予新值B,否则不做任何事儿。
CAS就是在一个循环里不断的做CAS操作,直到成功为止。
比如一个基于CAS实现更新的例子(伪代码):
1 | do{ |
CAS是一个CPU原子指令。
因为整个过程中并没有“加锁”和“解锁”操作,因此乐观锁策略也被称为无锁编程。换句话说,乐观锁其实不是“锁”,它仅仅是一个循环重试CAS的算法而已!
三、自旋锁
有一种锁叫自旋锁。所谓自旋,说白了就是一个 while(true) 无限循环。
刚刚的乐观锁就有类似的无限循环操作,那么它是自旋锁吗?
不是。尽管自旋与 while(true) 的操作是一样的,但还是应该将这两个术语分开。“自旋”这两个字,特指自旋锁的自旋。
四、synchronized锁升级:偏向锁 → 轻量级锁 → 重量级锁
锁升级总结
自己整理了一个流程图:
synchronized锁只会升级,不会降级。
- 无锁(即CAS操作)
- 偏向锁(偏向于一个进程,发生竞争则升级)
- 轻量级锁(自旋锁,自旋次数达到阈值默认10则升级)
- 重量级锁
前面提到,synchronized关键字就像是汽车的自动档,现在详细讲这个过程。一脚油门踩下去,synchronized会从无锁升级为偏向锁,再升级为轻量级锁,最后升级为重量级锁,就像自动换挡一样。
无锁
无锁的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。也就是CAS(CAS是基于无锁机制实现的)。
偏向锁
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。
偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。
轻量级锁
但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。
重量级锁
如果发生竞争,其他竞争线程会阻塞挂起,而不是忙等,直到被唤醒。
其他关于synchronized
五、可重入锁(递归锁)
当前线程持有锁,当前线程再次获取锁,可以得到,那就是可重入锁。
Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括synchronized关键字锁都是可重入的。
StampedLock是不可重入锁。
六、公平锁、非公平锁
如果多个线程申请一把公平锁,那么当锁释放的时候,先申请的先得到,非常公平。
显然如果是非公平锁,后申请的线程可能先获取到锁,是随机或者按照其他优先级排序的。
ReentrantLock可以传入参数指定是否公平锁。
synchronized是非公平锁。
七、可中断锁
这里的关键是理解什么是中断。Java并没有提供任何直接中断某线程的方法,只提供了中断机制。何谓“中断机制”?线程A向线程B发出“请你停止运行”的请求(线程B也可以自己给自己发送此请求),但线程B并不会立刻停止运行,而是自行选择合适的时机以自己的方式响应中断,也可以直接忽略此中断。也就是说,Java的中断不能直接终止线程,而是需要被中断的线程自己决定怎么处理。这好比是父母叮嘱在外的子女要注意身体,但子女是否注意身体,怎么注意身体则完全取决于自己。
回到锁的话题上来,如果线程A持有锁,线程B等待获取该锁。由于线程A持有锁的时间过长,线程B不想继续等待了,我们可以让线程B中断自己或者在别的线程里中断它,这种就是可中断锁。
1 | /* Lock接口 */ |
八、读写锁、共享锁、互斥锁
- 读锁,共享锁。
- 写锁,排他锁,互斥锁。
读锁,写锁,都是悲观锁。
JDK提供的唯一一个 ReadWriteLock
接口实现类是 ReentrantReadWriteLock
。看名字就知道,它不仅提供了读写锁,而是都是可重入锁。 除了两个接口方法以外,ReentrantReadWriteLock
还提供了一些便于外界监控其内部工作状态的方法,这里就不一一展开。
九、Java悲观锁乐观锁
我们在Java里使用的各种锁,几乎全都是悲观锁。
synchronized从偏向锁、轻量级锁到重量级锁,全是悲观锁。
JDK提供的Lock实现类全是悲观锁。
其实只要有“锁对象”出现,那么就一定是悲观锁。因为乐观锁不是锁,而是一个在循环里尝试CAS的算法。
那JDK并发包里到底有没有乐观锁呢?
有。java.util.concurrent.atomic
包里面的原子类都是利用乐观锁实现的。
问题:CAS与自旋的区别?也就是无锁和轻量级锁/自旋锁的区别?
答:貌似懂了,举个例子,修改变量值:CAS是不断读取变量值,并尝试操作;而自旋锁是不断检查修改值的这段代码锁是否可获取,并不去读具体的值。