常见锁策略:
不是只针对java的,别的语言,工具只要涉及到锁,也同样适用。
1.乐观锁Vs悲观锁
2.普通互斥锁Vs读写锁
3.轻量级锁vs重量级锁
4.自旋锁vs挂起等待锁
5.公平锁vs非公平锁
6.可重入锁Vs不可重入锁
(1)乐观锁vs悲观锁
锁的实现者,预测接下来锁冲突的概率大小。根据这个冲突的概率,来决定接下来怎么做?
锁冲突:就是锁竞争,两个线程针对一个对象加锁产生阻塞等待。
乐观锁:预测接下来冲突概率不大。
悲观锁:预测接下来冲突概率比较大。
两种锁导致最终要做的事情是不一样的。
(以下情况也并不绝对)
悲观锁一般要做的工作更多一些效率会更低。
乐观锁做的工作会更少一点, 效率更高。
站在加锁的过程上看待的,
加锁解锁过程中干的工作是多还是少?
(1)乐观锁,指的是干的事情少,轻量级。
(2)悲观锁,指的是干的事情多,重量级。
(2)读写锁
普通的互斥锁,就如同synchronized。
当两个线程竞争同一把锁,就会产生等待。
读写锁,分成两种情况:
1.读加锁
2.写加锁
产生竞争情况:
(1)读锁和读锁之间,不会产生竞争
(2)写锁和写锁之间,有竞争
(3)读锁和写锁之间,有竞争
多个线程同时读同一个变量,没影响(也就不必加锁)。
(3)重量级锁vs轻量级锁
轻量级锁加锁解锁开销比较小:
典型的,纯用户态的加锁逻辑,开销是比较小
重量级锁,加锁解锁开销比较大:
典型的,进入内核态的加锁逻辑,开销是比较大的
(4)自旋锁vs挂起等待锁
自旋锁是轻量级锁的一种典型实现:
通常是纯用户态的,
不需要经过内核态
(时间相对更短)。
自旋就类似于“忙等" ,占用CPU大量时间,持续
消耗CPU资源
,反复询问当前锁是否就绪。
挂起等待锁是重量级锁的一种典型实现:
通过内核的机制,
来实现挂起等待
(时间更长),挂起等待不消耗CPU资源,等待唤醒。
(5)公平锁 vs 非公平锁
假设三个线程 A, B, C. A 先尝试获取锁, 获取成功.
然后 B 再尝试获取锁, 获取失败, 阻塞等待;
然后
C 也尝试获取锁, C 也获取失败, 也阻塞等待.
当线程 A 释放锁的时候, 会发生啥呢?
公平锁: 遵守 "先来后到"。B 比 C 先来的. 当 A 释放锁的之后,B 就能先于 C 获取到锁。
非公平锁: 不遵守 "先来后到",B 和 C 都有可能获取到锁。
操作系统默认的锁的调度是非公平的情况
要想实现公平锁需要引入额外的数据结构,来记录线程加锁的顺序,
需要一定的额外开销。
(6)可重入锁vs不可重入锁:synchronized
可重入:同一个线程针对同一把锁,连续加锁两次,不会死锁。
不可重入:同一个线程针对同一把锁,连续加锁两次会死锁。
synchronized:
1.乐观锁&&悲观锁
2.轻量级锁&&重量级锁
3.乐观锁的部分是基于自旋锁实现的,悲观锁的部分是基于挂起等待锁实现的
从上面可以看出synchronized是自适应:
(a)初始使用的时候是乐观锁/轻量级锁/自旋锁
(b)如果锁竞争不激烈,就处于上述状态不变。
JMM在实现synchronized时候给我们提供自动优化的策略。
synchronized可以 自动适应多种不同的场景,比较
“智能"。
4.不是读写锁,是普通互斥锁。
5.是非公平锁
6.是可重入锁
在4,5,6中标准库中也有另外其他的锁能够实现。