一句话撸完重量级锁、自旋锁、轻量级锁、偏向锁、悲观、乐观锁等各种锁

重量级锁?自旋锁?自适应自旋锁?轻量级锁?偏向锁?悲观锁?乐观锁?执行一个方法咋这辛苦,到处都是锁。

这篇文章,给大家普及下这些锁究竟是啥,他们的由来,关系,区别。

重量级锁

要进入一个同步、线程安全的方法时,需先获得这个方法的锁,退出这个方法时,则释放锁。如果获取不到这个锁的话,意味着有别的线程在执行这个方法,这时就会马上进入阻塞的状态,等待那个持有锁的线程释放锁,然后再从阻塞的状态唤醒,再去获取这个方法的锁。

这种获取不到锁就马上进入阻塞状态的锁,称之为重量级锁。

自旋锁
线程从运行态进入阻塞态的过程非常耗时,因为不仅要保存线程此时的执行状态,上下文等数据,还涉及到用户态到内核态的转换。当然,把线程从阻塞态唤醒也一样,也非常耗时。

线程拿不到锁,就马上进入阻塞状态,然而现实是,它虽然这一刻拿不到锁,可能在下 0.0001 秒,就有其他线程把这个锁释放了。如果它慢0.0001秒来拿这个锁的话,可能就拿到了,不需经历阻塞/唤醒这个耗时的过程。

然而重量级锁就是,它不肯等待一下,拿不到就是马上进入阻塞状态。为解决这个问题,引入了另一种愿意等一段时间的锁 — 自旋锁。

自旋锁是,如果此时拿不到锁,并不马上进入阻塞状态,而是等一段时间,看这段时间有没其他线程把锁释放了。怎么等呢?这就类似线程在那里做空循环,如果循环一定的次数还拿不到锁,它才进入阻塞的状态。

至于是循环等待几次,这是可人为指定一个数字。

自适应自旋锁
上面说的自旋锁,每个线程循环等待的次数都一样,例如设为 100次的话,那线程在空循环 100 次后还没拿到锁,就会进入阻塞状态。

而自适应自旋锁就牛逼了,它无需人为指定循环几次,它自己会判断要循环几次,且每个线程可能循环的次数也不一样。而之所以这样做,主要是,如果一个线程在不久前拿到过这个锁,或它之前经常拿到这个锁,那么就认为它再次拿到锁的几率非常大,所以循环的次数会多一些。

而如果有些线程从没拿到过这个锁,或平时很少拿到,那就认为,它再次拿到的概率比较小,所以就让它循环的次数少一些。因为在那里做空循环很消耗 CPU。

所以这种能够根据线程最近获得锁的状态来调整循环次数的自旋锁,我们称之为自适应自旋锁。

轻量级锁
上面介绍的三种锁:重量级、自旋锁和自适应自旋锁,他们都有个特点,就是进入一个方法时,会加锁,退出一个方法时,就释放对应的锁。

之所以要加锁,是因为他们害怕自己在这个方法执行时,被别人偷偷进来,所以只能加锁,防止其他线程进来。

这实在是太麻烦了,如果根本没有线程来和他们竞争锁,那他们不是白白上锁了?要知道,加锁这个过程是需操作系统来帮忙,很消耗时间的。为解决这种动不动就加锁带来的开销,轻量级锁出现了。

轻量级锁认为,当在方法里面执行时,很少刚好有线程也来执行这个方法,所以,当进入一个方法时就不用加锁,只需做个标记就可以,即可以用一个变量来记录此时该方法是否在执行。如果这个方法没在执行,当进入这个方法时,采用CAS机制,把这个方法的状态标记为已有人在执行,退出这个方法时,在把这个状态改为没人在执行。

之所以要用CAS机制来改变状态,是因为对这个状态的改变,不是个原子性操作,所以需CAS机制来保证操作的原子性。不知道CAS的可以看这篇文章:并发的核心:CAS 是什么?Java8是如何优化 CAS

显然,比起加锁操作,采用CAS来改变状态的操作,花销就小多了。

然而,没人来竞争的想法,那是说的而已,如果万一有人来竞争说呢?即当一个线程执行一个方法时,方法已有人在执行了。

如果真遇到了竞争,就会认为轻量级锁已不适合,就会把轻量级锁升级为重量级锁了。

所以轻量级锁适合用在那种,很少出现多个线程竞争一个锁的情况,即适合那种多个线程总是错开时间来获取锁的情况。

偏向锁
偏向锁就更牛逼了,轻量级锁已够轻,然偏向锁更省事,偏向锁认为,轻量级锁每次进入一个方法都需用CAS改变状态,退出也要改变,较麻烦。

偏向锁认为,对于一个方法,是很少有两个线程来执行,其实也就一个线程在执行这个方法,相当于单线程,居然是单线程,那就没必要加锁。

不过毕竟实际情况的多线程,单线程只是自己认为而已,所以,偏向锁进入一个方法时这样处理:如果这个方法没人进来过,那一个线程首次进入这个方法时,会采用CAS机制,把这个方法标记为有人在执行,和轻量级锁类似,且也会把该线程的 ID 记录进去,相当于记了哪个线程在执行。

然后,但这个线程退出这个方法时,它不改变这个方法的状态,而是直接退出来,因为它认为除了自己这个线程外,其他线程并不会来执行这个方法。

然后当这个线程再次进入这个方法时,会判断这个方法的状态,如果这个方法已被标记为有人在执行,且线程ID是自己,那它就直接进入这个方法执行

多方便,第一次进入需CAS机制来设置,以后进出就直接进入退出。

然而毕竟实际情况还是多线程,所以万一有其他线程来进入这个方法呢?如果真出现这种情况,其他线程一看这个方法的ID不是自己,这时说明,至少有两个线程要来执行这个方法论,这意味着偏向锁已不适用了,这个时候就会从偏向锁升级为轻量级锁。

所以,偏向锁适用于那种,始终只有一个线程在执行一个方法的情况。

为方便大家理解,讲轻量级锁和偏向锁时,简化了很多,不然会涉及到对象的内部结构、布局,把那些扯出来,可能就晕了,所以大致讲了原理。

悲观锁和乐观锁
最开始说的三种锁,重量级锁、自旋锁和自适应自旋锁,进入方法前,就一定要先加个锁,这种为称为悲观锁。悲观锁总认为,如果不事先加锁的话,就会出事。

而乐观锁却相反,认为不加锁也没事,可先不加锁,如果出现了冲突,再想办法解决,如 CAS 机制,上面说的轻量级锁,就是乐观锁的。不会马上加锁,而是等出现了冲突,再想办法解决。不知道 CAS 机制的,可看这篇文章哦:并发的核心:CAS 是什么?Java8是如何优化 CAS 的?。

总结
到这里也大致写完了,简单介绍普及了一下,重点的大家要理解他们的由来,原理。每一种锁都有他们的应用以及各自的优缺点,如果有机会,我再给大家说说他们各自的应用场景,优缺点,这个面试的时候,好像也会被经常到,今天先写到这里勒。

大家可以说说这些锁的优缺点哦,例如与重量级锁相比,自旋锁容量导致什么问题的发生?悲观锁和乐观锁的比较呢?

转自 一句话撸完重量级锁、自旋锁、轻量级锁、偏向锁、悲观、乐观锁等各种锁 ---- 不看后悔系列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值