目录
一、锁策略
1.1 乐观锁 vs 悲观锁
这里的乐观锁和悲观锁不是指的某一个具体的锁,而是指的“一类锁”
乐观锁:预测锁竞争不是很激烈(这里做的工作相对较少)。一开始的时候是不会加锁的,会引入一个版本号,借助版本号识别出当前的数据访问时候冲突
悲观锁:预测锁竞争是很激烈的(这里做的工作比较多)。一开始就会去加锁,会在每次访问共享变量之前去真正加锁。
区别:对锁竞争预测的激烈程度,以及后续工作的多少
1.2 轻量级锁 vs 重量级锁
轻量级锁:加锁机制尽可能不使用 mutex,而是尽量在用户态代码完成。实在搞不定了,再使用 mutex。少量的内核态用户态切换,不太容易引发线程调度。加锁的开销比较小(花的时间少,占用系统资源比较少,效率比较高)
重量级锁:加锁机制重度依赖了 OS 提供了 mutex,大量的内核态用户态切换很容易引发线程的调度。加锁的开销比较大(花的时间多,占用系统资源比较多,效率比较低)
1.3 自旋锁 vs 挂起等待锁
自旋锁:轻量级锁的一种典型实现。如果获取锁失败,立即再尝试获取锁,无限循环,直到获取到锁为止。第一次获取锁失败,第二次的尝试会在极短的时间内到来。一旦锁被其他线程释放,就能第一时间获取到锁。
- 优点: 没有放弃 CPU,不涉及线程阻塞和调度,一旦锁被释放,就能第一时间获取到锁。
- 缺点: 如果锁被其他线程持有的时间比较久,那么就会持续的消耗 CPU 资源。(而挂起等待的时候是不消耗 CPU 的)。
挂起等待锁:重量级锁的一种典型实现。通过内核态,借助系统提供的锁机制,当出现锁冲突的时候,会牵扯到内核对于线程的调度,使冲突的线程出现挂起等待(阻塞等待)
- 优点:在挂起等待的过程中,不会去消耗cpu资源
- 缺点:在等待的过程中,上一个线程释放了锁需要一定的时间才能获取到,无法保证第一时间获取到锁。
1.4 读写锁 vs 互斥锁
读写锁:多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同一个锁,就会产生极大的性能损耗。所以这里就产生了读写锁
- 读加锁和读加锁之间,不互斥。(只读不会产生多线程安全问题,所以在读操作的过程中不加锁,会提高效率)
- 写加锁和写加锁之间,互斥。
- 读加锁和写加锁之间,互斥。
互斥锁:就是synchronized这样的锁,提供了加锁解锁两个操作,如果一个线程加锁了,另一个线程也尝试加锁,就会阻塞等待
1.5 公平锁 vs 非公平锁
公平锁:遵循“先来后到”,如果A获取了锁B与C都在等待且B排在C的前面,那么当A释放锁的时候,就应该是B先拿到锁。
非公平锁:不遵循“先来后到”,释放锁的时候谁抢到就是谁的
注意:这里的公平和非公平并没有好坏之分,只是取决于不同的应用场景!!!
1.6 可重入锁 vs 不可重入锁
可重入锁:针对一把锁重复加锁不会产生死锁
不可重入锁:针对一把锁重复加锁会产生死锁