重量级锁?自旋锁?自适应自旋锁?轻量级锁?偏向锁?悲观锁?乐观锁?执行一个方法咋这么辛苦,处处都是锁。面试
今天这篇文章,给你们普及下这些锁到底是啥,他们的由来,他们之间有啥关系,有啥区别。算法
若是你学过多线程,那么你确定知道锁这个东西,至于为何须要锁,我就不给你普及了,就当作你是已经懂的了。安全
咱们知道,咱们要进入一个同步、线程安全的方法时,是须要先得到这个方法的锁的,退出这个方法时,则会释放锁。若是获取不到这个锁的话,意味着有别的线程在执行这个方法,这时咱们就会立刻进入阻塞的状态,等待那个持有锁的线程释放锁,而后再把咱们从阻塞的状态唤醒,咱们再去获取这个方法的锁。多线程
这种获取不到锁就立刻进入阻塞状态的锁,咱们称之为重量级锁。并发
咱们知道,线程从运行态进入阻塞态这个过程,是很是耗时的,由于不只须要保存线程此时的执行状态,上下文等数据,还涉及到用户态到内核态的转换。固然,把线程从阻塞态唤醒也是同样,也是很是消耗时间的。工具
刚才我说线程拿不到锁,就会立刻进入阻塞状态,然而现实是,它虽然这一刻拿不到锁,可能在下 0.0001 秒,就有其余线程把这个锁释放了。若是它慢0.0001秒来拿这个锁的话,可能就能够顺利拿到了,不须要经历阻塞/唤醒这个花时间的过程了。布局
然而重量级锁就是这么坑,它就是不愿等待一下,一拿不到就是要立刻进入阻塞状态。为了解决这个问题,咱们引入了另一种愿意等待一段时间的锁 --- 自旋锁。优化
自旋锁就是,若是此时拿不到锁,它不立刻进入阻塞状态,而是等待一段时间,看看这段时间有没其余人把这锁给释放了。怎么等呢?这个就相似于线程在那里作空循环,若是循环必定的次数还拿不到锁,那么它才会进入阻塞的状态。操作系统
至因而循环等待几回,这个是能够人为指定一个数字的。.net
上面咱们说的自旋锁,每一个线程循环等待的次数都是同样的,例如我设置为 100次的话,那么线程在空循环 100 次以后还没拿到锁,就会进入阻塞状态了。
而自适应自旋锁就牛逼了,它不须要咱们人为指定循环几回,它本身自己会进行判断要循环几回,并且每一个线程可能循环的次数也是不同的。而之因此这样作,主要是咱们以为,若是一个线程在不久前拿到过这个锁,或者它以前常常拿到过这个锁,那么咱们认为它再次拿到锁的概率很是大,因此循环的次数会多一些。
而若是有些线程历来就没有拿到过这个锁,或者说,平时不多拿到,那么咱们认为,它再次拿到的几率是比较小的,因此咱们就让它循环的次数少一些。由于你在那里作空循环是很消耗 CPU 的。
因此这种可以根据线程最近得到锁的状态来调整循环次数的自旋锁,咱们称之为自适应自旋锁。
上面咱们介绍的三种锁:重量级、自旋锁和自适应自旋锁,他们都有一个特色,就是进入一个方法的时候,就会加上锁,退出一个方法的时候,也就释放对应的锁。
之因此要加锁,是由于他们惧怕本身在这个方法执行的时候,被别人偷偷进来了,因此只能加锁,防止其余线程进来。这就至关于,每次离开本身的房间,都要锁上门,人回来了再把锁解开。
这实在是太麻烦了,若是根本就没有线程来和他们竞争锁,那他们不是白白上锁了?要知道,加锁这个过程是须要操做系统这个大佬来帮忙的,是很消耗时间的,。为了解决这种动不动就加锁带来的开销,轻量级锁出现了。
轻量级锁认为,当你在方法里面执行的时候,实际上是不多恰好有人也来执行这个方法的,因此,当咱们进入一个方法的时候根本就不用加锁,咱们只须要作一个标记就能够了,也就是说,咱们能够用一个变量来记录此时该方法是否有人在执行。也就是说,若是这个方法没人在执行,当咱们进入这个方法的时候,采用CAS机制,把这个方法的状态标记为已经有人在执行,退出这个方法时,在把这个状态改成了没有人在执行了。
之因此要用CAS机制来改变状态,是由于咱们对这个状态的改变,不是一个原子性操做,因此须要CAS机制来保证操做的原子性。不知道CAS的能够看这篇文章:并发的核心:CAS 是什么?Java8是如何优化 CAS 的?。
显然,比起加锁操做,这个采用CAS来改变状态的操做,花销就小多了。
然而可能会说,没人来竞争的这种想法,那是你说的而已,那若是万一有人来竞争说呢?也就是说,当一个线程来执行一个方法的时候,方法里面已经有人在执行了。
若是真的遇到了竞争,咱们就会认为轻量级锁已经不适合了,咱们就会把轻量级锁升级为重量级锁了。
因此轻量级锁适合用在那种,不多出现多个线程竞争一个锁的状况,也就是说,适合那种多个线程老是错开时间来获取锁的状况。
偏向锁就更加牛逼了,咱们已经以为轻量级锁已经够轻,然而偏向锁更加省事,偏向锁认为,你轻量级锁每次进入一个方法都须要用CAS来改变状态,退出也须要改变,多麻烦。
偏向锁认为,其实对于一个方法,是不多有两个线程来执行的,搞来搞去,其实也就一个线程在执行这个方法而已,至关于单线程的状况,竟然是单线程,那就不必加锁了。
不过毕竟实际状况的多线程,单线程只是本身认为的而已了,因此呢,偏向锁进入一个方法的时候是这样处理的:若是这个方法没有人进来过,那么一个线程首次进入这个方法的时候,会采用CAS机制,把这个方法标记为有人在执行了,和轻量级锁加锁有点相似,而且也会把该线程的 ID 也记录进去,至关于记录了哪一个线程在执行。
而后,但这个线程退出这个方法的时候,它不会改变这个方法的状态,而是直接退出来,懒的去改,由于它认为除了本身这个线程以外,其余线程并不会来执行这个方法。
而后当这个线程想要再次进入这个方法的时候,会判断一下这个方法的状态,若是这个方法已经被标记为有人在执行了,而且线程的ID是本身,那么它就直接进入这个方法执行,啥也不用作
你看,多方便,第一次进入须要CAS机制来设置,之后进出就啥也不用干了,直接进入退出。
然而,现实老是残酷的,毕竟实际状况仍是多线程,因此万一有其余线程来进入这个方法呢?若是真的出现这种状况,其余线程一看这个方法的ID不是本身,这个时候说明,至少有两个线程要来执行这个方法论,这意味着偏向锁已经不适用了,这个时候就会从偏向锁升级为轻量级锁。
因此呢,偏向锁适用于那种,始终只有一个线程在执行一个方法的状况哦。
这里我做下说明,为了方便你们理解,我在将轻量级锁和偏向锁的时候,实际上是简化了不少的,否则的话会涉及到对象的内部结构、布局,我以为把那些扯出来,大家可能要晕了,因此我大体讲了他们的原理。
最开始咱们说的三种锁,重量级锁、自旋锁和自适应自旋锁,进入方法以前,就必定要先加一个锁,这种咱们为称之为悲观锁。悲观锁总认为,若是不事先加锁的话,就会出事,这种想法确实悲观了点,这估计就是悲观锁的来源了。
而乐观锁却相反,认为不加锁也没事,咱们能够先不加锁,若是出现了冲突,咱们在想办法解决,例如 CAS 机制,上面说的轻量级锁,就是乐观锁的。不会立刻加锁,而是等待真的出现了冲突,在想办法解决。不知道 CAS 机制的,能够看我以前写的这篇文章哦:并发的核心:CAS 是什么?Java8是如何优化 CAS 的?。
到这里也大体写完了,简单介绍普及了一下,重点的你们要理解他们的由来,原理。每一种锁都有他们的应用以及各自的优缺点,若是有机会,我再给你们说说他们各自的应用场景,优缺点,这个面试的时候,好像也会被常常到,今天先写到这里勒。
你们能够说说这些锁的优缺点哦,例如与重量级锁相比,自旋锁容量致使什么问题的发生?悲观锁和乐观锁的比较呢?你们也能够评论区说说勒,这些是必定要搞懂的哦。
最后推荐下个人公众号:苦逼的码农,主要分享一下技术文章、面试题、算法题,各类工具、视频资源等,里面已有100多篇原创文章,期待各路英雄来交流,点击便可扫码关注戳我便可关注