最近复习了一遍锁策略,虽然锁策略在实际工作中需求不大,但经常出现在面试题中,以下帮助读者快速熟悉常见的锁策略
乐观锁与悲观锁
悲观锁(锁冲突概率高,工作复杂)
悲观锁认为多个线程访问同一个共享变量冲突概率大,会在每次访问前真正的加锁,悲观锁的实现就是先加锁, 获取到锁再操作数据. 获取不到锁就等待
乐观锁(锁冲突概率低,工作简单)
乐观锁认为多个线程访问同一个共享变量冲突的概率不大. 并不会真的加锁, 而是直接尝试访问数
据. 在访问的同时识别当前的数据是否出现访问冲突.乐观锁的实现可以引入一个版本号. 借助版本号识别出当前的数据访问是否冲突.
例如:
A和B是合租的室友,A去上厕所比较乐观,决定B不会进来,就不给厕所锁门了,B比较悲观,觉得A一天天的很喜欢上厕所,担心A突然闯进厕所,所以必须锁门
读写锁与普通互斥锁
读写锁就是把读操作和写操作分别进行加锁
多线程之间,当多个线程同时读取数据不会产生线程安全问题,但是同时写入数据或者边写边读就会导致线程不安全,因此就需要使用普通互斥锁(synchronized),但是如果只涉及读取,使用互斥锁会造成极大的性能损耗,因此出现了读取锁
读写锁需要在执行时候表明此次加锁的目的,是读还是写呢,如果是读不需要互斥,如果是写需要互斥。
读锁和读锁,不竞争
读锁和写锁,有竞争
写锁和写锁,有竞争
重量级锁与轻量级锁
重量级锁:加锁解锁的开销大,用户态的加锁逻辑
轻量级锁:加锁解锁的开销小,进入内核态的加锁逻辑
理解内核态和用户态:
例如去银行办理业务,我需要复印一份存折,此时自己去银行的复印件操作属于用户态,让柜台工作人员帮我去操作属于内核态,内核态时,工作人员可能和其他人说话或者给其他客人解决问题,所以导致时间不确定,因此导致效率低下。
自旋锁与挂起等待锁
举一个简单的例子,小A(自旋锁)和小B(挂起等待锁)同时追求一个女生,此时这个女生有男朋友,两个人采取不同的追求策略,小A就死等这个女生分手,当这个女生分手后,小A马上获得了和该女生交往的机会,小B在女生有对象的时候,没有在一颗树上吊死,去做了其他的事情,当这个女生分手后的很长时间,询问小B要不要交往(此时女生可能已经换了很多男朋友了)
自旋锁是一种典型的轻量级锁的实现方式
优点:没有放弃cpu,不涉及阻塞和调度,一旦锁被释放,第一时间获取到锁
缺点:如果锁被其他线程占用时间过长,会持续消耗cpu资源
挂起等待锁是一种典型的重量级锁的实现方式
优点:等待时间里,cpu可以做点别的事情,节省cpu的资源浪费
缺点:获取锁的时间长
可重入锁与不可重入锁
可重入锁:
允许同一个线程多次获取同一把锁(synchronized是可重入锁)
不可重入锁:
允许同一个线程多次获取同一把锁
公平锁与非公平锁
如果有ABC三个线程,当A线程先尝试获取锁,获取成功后,B线程尝试获取锁,锁被占用,获取失败,阻塞等待,最后C线程也尝试获取锁,也获取失败,阻塞等待。
当A线程释放锁后,会出现什么情况呢?
公平锁:遵循“先来后到”,B比C先来的当A释放锁后,B就先于C获取锁
非公平锁:不遵循"先来后到",即使B比A先来,但是B和C都有可能获取到锁,取决于操作系统的调度