目录
乐观锁:预测锁冲突的概率较低,多个线程在访问锁对象时不会真的加锁,代码中需要做的工作量比较少。
悲观锁:预测锁冲突的概率较高,多个线程在访问锁对象时都会进行真加锁,代码中需要做更多的工作。
举个栗子:
假设有两对情侣:男一号和女一号,男二号和女二号。
乐观锁:男一号认为自己和女一号感情非常好,女一号不会和其他男生搞暧昧,所以偶尔才会问一下女一号:“你爱我吗?”
悲观锁:男二号总是害怕女二号会绿他,即使在他们感情非常好的时候,他都要每天问女二号:“你爱我吗?”
普通互斥锁:多个线程竞争同一把锁时,当一个线程竞争到锁之后,其他线程就阻塞等待。比如synchronized。
读写锁分为两种情况:
加读锁:多个线程同时读一份数据时,不会有线程不安全的问题,所以没必要加锁。
加写锁:多个线程同时写一份数据,或者有的线程写有的线程读同一份数据时,会有竞争,也就会有线程不安全问题,此时需要加锁。
实际开发中,读的场景一般会多于写的场景,所以使用读写锁就会减少很多锁竞争的情况,优化程序的效率。
轻量级锁:加锁、解锁的开销比较小。
纯用户态的加锁逻辑,不涉及到进入内核的操作,因此开销比较小。
重量级锁:加锁、解锁的开销比较大。
需要进入内核态的加锁逻辑,开销就比较大。
注意:
轻量级锁和重量级锁是站在加锁解锁的结果来进行区分:
重量级锁:加锁、解锁操作消耗的时间多。
轻量级锁:加锁、解锁操作消耗的时间少。
乐观锁和悲观锁是站在加锁的过程来进行区分:
乐观锁:加锁的过程中需要做的工作比较少。
悲观锁:加锁的过程中需要做的工作比较多。
因此,乐观锁一般也是轻量级锁,悲观锁一般也是重量级锁,但这个结论并不绝对~
自旋锁:线程A和线程B竞争同一把锁,线程A竞争到之后,线程B会立刻尝试重新获取锁,无限循环,直到获取到锁为止。所以只要锁被线程A释放,线程B就会第一时间获取到锁,但是这样会消耗大量的CPU资源。自旋锁是纯用户态实现的。
挂起等待锁:线程A和线程B竞争同一把锁,线程A竞争到之后,线程B进行阻塞等待,此时CPU就会空出来可以做其他的工作,但线程B获取到锁的时机可能并不会很及时。
举个栗子:
假设男生A约女生B一起吃饭,男生A早早来到餐厅等待~
自旋锁:男生A会不停地打电话问女生B:“你到了吗?”当女生B到餐厅时,男生A就会立刻知道。
挂起等待锁:男生A到餐厅后打个电话问女生B:“你到了吗?”女生B说:“还没有~”
于是男生A就拿出手机刷视频,打游戏……所以当女生B到达餐厅时,男生A并不一定能及时知道。
自旋锁适用于锁持有时间比较短的场景,更高效。如果锁的持有时间比较长,就会浪费CPU资源。
挂起等待锁则适用于锁持有时间比较长的场景,可以合理利用CPU资源。
公平锁:多个线程竞争同一个锁对象时,会遵循先来后到的规则。
非公平锁:多个线程竞争同一个锁对象时,不遵循先来后到,而是谁抢到就是谁的。
操作系统默认的锁的调度是非公平锁,要想实现公平锁,需要引入额外的数据结构来记录每个线程加锁的顺序,所以也需要花费额外的开销。
举个栗子:
假设有一群人都在等同一辆公交车~
公平锁:所有人都自觉地排队上车,先来的排在前面,后来的排在后面。
非公平锁:一群人都不讲武德,挤着上车。
可重入锁:一个线程连续加锁两次,不会出现死锁的问题,比如synchronized。
synchronized会记录当前持有锁的线程,如果发现当前线程在释放锁之前再次进行加锁,就会让计数器自增,线程解锁,则计数器自减,当计数器为0时进行解锁操作,线程才会真正释放锁。
不可重入锁:一个线程连续加锁两次,会出现死锁的问题。