目录
1.重(轻)量级锁和(悲)乐观锁
重量级锁:锁冲突的程度大,主要依赖操作系统提供的锁,也就是区分用户态和内核态,两种状态的切换使得线程在重量级锁的作用下会进行阻塞,等待上一个锁的释放.
轻量级锁:锁冲突的程度小,主要依赖用户态来完成功能,当数据会被其他线程修改的时候,返回一个错误,让用户自己决定接下来要怎么执行.
乐观锁:经常会是轻量级锁
悲观锁:经常会是重量级锁,悲观锁会认为每次数据都有被修改的风险,所以每次都会上锁.
乐观锁和悲观锁可以理解为,我去问老师问题,悲观锁认为老师每次都在,所以不管老师在不在都回去问老师问题(每次都加锁),乐观锁去问老师,老师在就直接问老师(不加锁直接访问),老师不在就下次再来(虽然没加锁,但是会识别出数据是否冲突,并且让用户自己决定接下来怎么执行)
用户态和内核态可以理解为我们去银行办理业务,我们可以去窗口排队询问工作人员办理(内核态时间不可控,需要阻塞等待),也可以自己去自助办理(用户态时间可控,不用阻塞等待)
2.自旋锁与挂起等待锁
自旋锁:当锁冲突时,会发生阻塞等待,这时线程会离开CPU进入阻塞队列,其实大部分情况下,阻塞等待的时间不长,所以这种情况下线程可以不用离开CPU在CPU里,一旦锁被其他线程释放,就能第一时间获取到锁
挂起等待锁:相当于自旋锁的反面例子,锁冲突时线程会离开CPU进行阻塞等待,等到锁被其他线程释放了再进入CPU运行.
这种情形可以理解为:
我想追求女神,跟女神表白但是女神已经有男朋友了
挂起等待锁:沉浸在悲伤的情绪中久久不能自拔,感觉生活没有了希望,但过了很久(阻塞等待),女神不知道换了多少任男友后突然向我这个备胎表白了,这时候才能跟女神谈恋爱(进入运行)
自旋锁:每天坚持跟女神表白,死皮赖脸直到她分手后立即上位(在CPU内空转,但是能第一时间获取到锁)
可以看出来自旋锁是一种轻量锁的表现形式,大大减少了线程的调度,缺点就是消耗CPU资源
synchronized的轻量锁策略就是通过自旋锁的形式实现的
3.公平锁和非公平锁
公平锁:遵循先来后到,先尝试获取锁的线程获取成功,后者只能阻塞等待
非公平锁:机会均等,先来和后来都有一样的概率能获取到锁.
4.可重入锁和不可重入锁
可重入锁:允许同一个线程多次获取同一把锁
不可重入锁:同一个线程多次获取同一把锁会造成死锁
// 第一次加锁 , 加锁成功lock ();// 第二次加锁 , 锁已经被占用 , 阻塞等待 .lock ();
5.CAS和synchronized的优化策略
CAS保证原子性,操作都是原子的,实现了Java.util.concurrent.atomic包里面的类
CAS实现自旋锁,下面是伪代码:当锁被其他线程占用,则自旋等待不退出循环,如果这个锁没有被其他线程占用,则退出循环,进行下一步的解锁
public class SpinLock {
private Thread owner = null;
public void lock(){
// 通过 CAS 看当前锁是否被某个线程持有.
// 如果这个锁已经被别的线程持有, 那么就自旋等待.
// 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.
while(!CAS(this.owner, null, Thread.currentThread())){
}
}
public void unlock (){
this.owner = null;
}
}
CAS可以理解为一个乐观锁,因为他的伪代码实现:当数据冲突时不会阻塞等待,只会返回错误
对比前后修改值的时候也会根据版本号进行判断(解决ABA问题的bug)
boolean CAS(address, expectValue, swapValue) {
if (&address == expectedValue) {
&address = swapValue;
return true;
}
return false;
}
synchronized:
1.开始是乐观锁,锁冲突频繁时转换为悲观锁
2.开始是轻量级锁,锁被持有的时间长转换成重量级锁
3.实现轻量级锁用自旋锁策略
4.是一种不公平锁,分先来后到,等到先来的线程释放锁后另外的线程可以运行
5.可重入锁
*6.不是读写锁
synchronized的加锁过程:
无锁 -> 偏向锁 -> 自旋锁 -> 重量级锁
偏向锁:不是真的锁,只是在被锁的对象头做个标记,记录这个锁属于当前的线程
本质上是延迟加锁,等到有线程来竞争时就立马加锁
就好比如男女朋友,即使两个人不结婚也能幸福生活,但是有其他竞争者来时,还是需要用法定夫妻关系维持稳定的生活,杜绝外来者有非分之想