锁的介绍和分类(轻量级锁 重量级锁 偏向锁 自旋锁 互斥锁)

导读:本篇文章讲解 锁的介绍和分类(轻量级锁 重量级锁 偏向锁 自旋锁 互斥锁),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

目录

公平锁 非公平锁

非公平锁

公平锁

可重入锁 不可重入锁

可重入锁(递归锁)

不可重入锁

轻量级锁 重量级锁  偏向锁

重量级锁

自旋锁(循环上锁)

轻量级锁

轻量级锁的释放

偏向锁

自旋锁和互斥锁

自旋锁

互斥锁

为何要使用自旋锁

自旋锁可能潜在的问题

参考:


公平锁 非公平锁

非公平锁

非公平锁是抢占式的,有优先级区分的线程争夺锁。
包括:
synchronized关键字
ReentrantLock默认创建的也是非公平锁

公平锁

公平锁是先到先得的原则,排队获取。
new ReentrantLock(true)

可重入锁 不可重入锁

可重入锁(递归锁)

可重入锁也叫递归锁,指的是同步方法内调用加锁方法,只需要获取最外层锁即可畅通无阻,内部的其他锁不再需要获取,可直接访问。这种设计可以极大的减少锁控制(如果是可重入锁,只需要控制最外层的锁即可),避免锁控制混乱或释放不及时导致死锁发生的概率。

不可重入锁

只能进入一次,在同步方法内如果要获取同一把锁就会死锁。

轻量级锁 重量级锁  偏向锁

重量级锁

和monitor相关联属于重量级锁,因为有和操作系统的交互,会占用较多资源,所以有了轻量级锁和偏向锁。三者可以在对象的markword字段中后三位二进制数得到,01是正常状态和偏向锁状态,00是轻量级锁,10是重量级锁。

自旋锁(循环上锁)

重量级锁竞争的时候可以使用自旋来进行优化。

重量级锁会有阻塞,阻塞再唤醒就会有线程的上下文切换,效率较低。

于是在一个线程上锁时发现对象已经被占用,这时不会立马进入阻塞队列,而是会连续几次进行上锁的尝试。

这样会减少一些线程的阻塞,提升效率。自旋锁在单核cpu下没有意义,反而降低程序效率。

轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。

如果没有锁膨胀,轻量级锁没有和操作系统的交互。

创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word 。

在进行锁定时,通过CAS(compare and swap)和该对象头中的mark word进行比较交换,如果成功了,那么此时 锁记录中就会存放对象头的mark word( hashcode epoch age等数据),对象的mark word就会被替换为一个64位的二进制数(末尾是00 表明是轻量级锁),表示该对象被某个线程锁了。

锁的介绍和分类(轻量级锁 重量级锁 偏向锁 自旋锁 互斥锁)

如果CAS不成功,就会进入锁重入或者锁膨胀(升级为重量级锁)

上锁过程,如果该线程的子方法里面还要加锁,那么就会发生锁重入,即再在前面的栈帧上方建立新的栈帧,里面同样维护着一个lock record内存,引用的对象依然时同一个,不过存放mark word 的地方存放null,因为不能进行CAS了。

锁膨胀就是当另一个线程想要CAS一个对象时,发现该对象已经被上锁,那么这时就会进入锁膨胀:

为该对象申请一个 monitor对象,然后第一个线程的栈帧中锁记录中的的object的地址就不是刚才的轻量级锁的标识了,就会指向monitor的地址,表明自己是一个重量级锁锁定的对象,同时后一个线程进入 entrylist,阻塞。

锁的介绍和分类(轻量级锁 重量级锁 偏向锁 自旋锁 互斥锁)

轻量级锁的释放

线程释放锁的时候如果CAS成功,那么说明没有发生锁膨胀,正常CAS设置锁对象的mark word即可。

线程释放锁的时候,CAS失败,就会进入重量级锁的解锁流程,即找到monitor对象,把owner设置为null,唤醒entrylist中的blocked线程。

这样的话就比重量级锁的开销更小,效率更高。

偏向锁

进一步优化轻量级锁(CAS导致效率变低),使用线程id来替换markword

而不是采用锁记录CAS markword。

每次只需要检查id是不是一个线程的即可。

自旋锁和互斥锁

多线程中,对共享资源进行访问,为了防止并发引发的相关问题,一般都是引入锁的机制来处理并发问题。java获取到资源的线程A对这个资源加锁,其余线程好比B要访问这个资源首先要得到锁,而此时A持有这个资源的锁,只有等待线程A逻辑执行完,释放锁,这个时候B才能获取到资源的锁进而获取到该资源。

这个过程当中,A一直持有着资源的锁,那么没有获取到锁的其余线程好比B怎么办?一般就会有两种方式:

1. 一种是没有得到锁的进程就直接进入阻塞(BLOCKING),这种就是互斥锁缓存

2. 另一种就是没有得到锁的进程,不进入阻塞,而是一直循环着,看是否可以等到A释放了资源的锁。

自旋锁

自旋锁(spin lock)是一种非阻塞锁,也就是说,若是某线程须要获取锁,但该锁已经被其余线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取锁。

互斥锁

互斥量(mutex)是阻塞锁,当某线程没法获取锁时,该线程会被直接挂起,该线程再也不消耗CPU时间,当其余线程释放锁后,操做系统会激活那个被挂起的线程,让其投入运行。

而《linux内核设计与实现》常常提到两种态,一种是内核态,一种是用户态,对于自旋锁来讲,自旋锁使线程处于用户态,而互斥锁须要从新分配,进入到内核态。这里你们对内核态和用户态有个初步的认知就好了,用户态比较轻,内核态比较重

为何要使用自旋锁

互斥锁有一个缺点,他的执行流程是这样的 托管代码  – 用户态代码 – 内核态代码、上下文切换开销与损耗,假如获取到资源锁的线程A立马处理完逻辑释放掉资源锁,若是是采起互斥的方式,那么线程B从没有获取锁到获取锁这个过程当中,就要用户态和内核态调度、上下文切换的开销和损耗。因此就有了自旋锁的模式,让线程B就在用户态循环等着,减小消耗。

自旋锁比较适用于锁使用者保持锁时间比较短的状况,这种状况下自旋锁的效率要远高于互斥锁。

自旋锁可能潜在的问题

过多占用CPU的资源,若是锁持有者线程A一直长时间的持有锁处理本身的逻辑,那么这个线程B就会一直循环等待过分占用cpu资源。

参考:

黑马 JUC

不可重入锁(自旋锁)带来的好处 – JavaShuo

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/92798.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!