什么是自旋锁

多线程中,对共享资源进行访问,为了防止并发引起的相关问题,通常都是引入锁的机制来处理并发问题。

获取到资源的线程A对这个资源加锁,其他线程比如B要访问这个资源首先要获得锁,而此时A持有这个资源的锁,只有等待线程A逻辑执行完,释放锁,这个时候B才能获取到资源的锁进而获取到该资源。

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

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

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

上述的两种方式,学术上,就有几种不同的定义方式,大学的时候 学习的是C++, 《C++ 11》中就有这样的描述:

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

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

而《linux内核设计与实现》经常提到两种态,一种是内核态,一种是用户态,对于自旋锁来说,自旋锁使线程处于用户态,而互斥锁需要重新分配,进入到内核态。这里大家对内核态和用户态有个初步的认知就行了,用户态比较轻,内核态比较重。用户态和内核态这个也是linux中必备的知识基础,借鉴这个,可以进行很多程序设计语言API上的优化,就比如说javaio的部分,操作io的时候,先是要从用户态,进入内核态,再用内核态去操作输入输出设备的抽象,这里减少用户态到内核态的转换就是新io的一部分优化,后面再聊。

wiki中的定义如下:

自旋锁是计算机科学用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种忙等待。

自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。因此操作系统的实现在很多地方往往用自旋锁。Windows操作系统提供的轻型读写锁(SRW Lock)内部就用了自旋锁。显然,单核CPU不适于使用自旋锁,这里的单核CPU指的是单核单线程的CPU,因为,在同一时间只有一个线程是处在运行状态,假设运行线程A发现无法获取锁,只能等待解锁,但因为A自身不挂起,所以那个持有锁的线程B没有办法进入运行状态,只能等到操作系统分给A的时间片用完,才能有机会被调度。这种情况下使用自旋锁的代价很高。(红字部分是我给wiki编辑的词条,单核CPU不适合自旋锁,这个也只是针对单核单线程的情况,现在的技术基本单核都是支持多线程的)

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

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

自旋锁可能潜在的问题
过多占用CPU的资源,如果锁持有者线程A一直长时间的持有锁处理自己的逻辑,那么这个线程B就会一直循环等待过度占用cpu资源
递归使用可能会造成死锁,不过这种场景一般写不出来
CAS
就不写术语定义了,简单的理解就是这个CAS是由操作系统定义的,由若干指令组成的,这个操作具有原子性,这些指令如果执行,就会全部执行完,不会被中断。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

CAS的问题
经典的CAS的ABA问题,上面提到了CAS操作的时候,要检测值有没有变化,如果一个值原来是A,后来变成了B, 后来又变成了A,CAS会认为没有发生变化。
       解决方案:

1. 加版本号   1A - 2B -  3A

2. 对java而言,jdk1.5提供了AtomicStampedReference来解决这个问题

只能保证一个共享变量的原子操作 
        CAS通常是对一个变量来进行原子操作的,所以如果对多个变量进行原子操作就会有问题了。

解决方案

1. 简单粗暴,加锁,反而加入了复杂性,最low的方式

2. 跟上面的加版本号的道理一样,就是将多个变量拼成一个变量(可以拼成一个字符串)

3. 对java而言,jdk1.5 提供了AtomicStampedReference,这个reference 就是个对象引用,把多个变量放在这个对象里即可

JAVA CAS封装
 sun.misc.Unsafe是JDK里面的一个内部类,这个类当中有三个CAS的操作

JAVA自旋锁应用
Jdk1.5以后,提供了java.util.concurrent.atomic包,这个包里面提供了一组原子类。基本上就是当前获取锁的线程,执行更新的方法,其他线程自旋等待,比如atomicInteger类中的getAndAdd方法内部实际上使用的就是Unsafe的方法。

/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the previous value
*/
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
当然java中的syncronized关键字,在1.5中有了很大的优化,加入了偏隙锁也有人叫偏向锁,主要的实现方式就是在对象头markword中打上线程的信息,这样资源上的锁的获取就偏向了这个线程,后面,会涉及一系列的锁升级的问题,间隙锁 - 轻量锁 - 重量级锁 ,锁升级后面单独抽出来写一篇,这个轻量锁实际上就是使用的也是自旋锁的实现方式。
---------------------
作者:csucoderlee
来源:CSDN
原文:https://blog.csdn.net/u010372981/article/details/81463201
版权声明:本文为博主原创文章,转载请附上博文链接!

原文地址:https://www.cnblogs.com/quinnsun/p/10434873.html

时间: 2024-12-29 07:34:22

什么是自旋锁的相关文章

Java线程 - CAS自旋锁(spin-lock)

一.自旋锁提出的背景 由于在多处理器系统环境中有些资源因为其有限性,有时需要互斥访问(mutual exclusion),这时会引入锁的机制,只有获取了锁的进程才能获取资源访问.即是每次只能有且只有一个进程能获取锁,才能进入自己的临界区,同一时间不能两个或两个以上进程进入临界区,当退出临界区时释放锁.设计互斥算法时总是会面临一种情况,即没有获得锁的进程怎么办?通常有2种处理方式.一种是没有获得锁的调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,这就是自旋锁,他不用将县城阻塞起来(NON

原子属性与非原子属性,互斥锁与自旋锁介绍

nonatomic 非原子属性 非线程安全,适合内存小的移动设备(手机,平板...) atomic 原子属性(线程安全,但需要消耗大量资源)针对多线程设计的,为默认值,保证同一时间只有一个线程能够写入;本身就是一把自旋锁;单写多读,单个线程写入,多个线程读取 注意:当重写属性的get与set方法时需要在@implementation后添加:@synthesiae 属性名 = _属性名; 互斥锁与自旋锁对比 互斥锁:如果发现其他线程正在执行锁定代码,线程会进入休眠(就绪状态),等其他线程时间到打开

互斥锁与自旋锁

1.互斥锁原理 在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性.每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象. 互斥锁,是一种信号量,常用来防止两个进程或线程在同一时刻访问相同的共享资源.可以保证以下三点: (1)原子性:把一个互斥量锁定为一个原子操作,这意味着操作系统(或pthread函数库)保证了如果一个线程锁定了一个互斥量,没有其他线程在同 一时间可以成功锁定这个互斥量. (2)唯一性:如果一个线程锁定了一个互

一个无锁消息队列引发的血案:怎样做一个真正的程序员?(二)——月:自旋锁

前续 一个无锁消息队列引发的血案:怎样做一个真正的程序员?(一)——地:起因 一个无锁消息队列引发的血案:怎样做一个真正的程序员?(二)——月:自旋锁 平行时空 在复制好上面那一行我就先停下来了,算是先占了个位置,虽然我知道大概要怎么写,不过感觉还是很乱. 我突然想到,既然那么纠结,那么混乱,那么不知所措,我们不如换个视角.记得高中时看过的为数不多的长篇小说<穆斯林的葬礼>,作者是:霍达(女),故事描写了两个发生在不同时代.有着不同的内容却又交错扭结的爱情悲剧,一个是“玉”的故事,一个是“月”

自旋锁

自旋锁是一个互斥设备,它只有两个值:“锁定”和“解锁”.它通常实现为某个整数值中的某个位.希望获得某个特定锁得代码测试相关的位.如果锁可用,则“锁定”被设置,而代码继续进入临界区:相反,如果锁被其他人获得,则代码进入忙循环(而不是休眠,这也是自旋锁和一般锁的区别)并重复检查这个锁,直到该锁可用为止,这就是自旋的过程.“测试并设置位”的操作必须是原子的,这样,即使多个线程在给定时间自旋,也只有一个线程可获得该锁. 自旋锁最初是为了在多处理器系统(SMP)使用而设计的,但是只要考虑到并发问题,单处理

【转载】同步和互斥的POSIX支持(互斥锁,条件变量,自旋锁)

上篇文章也蛮好,线程同步之条件变量与互斥锁的结合: http://www.cnblogs.com/charlesblc/p/6143397.html 现在有这篇文章: http://blog.csdn.net/goodluckwhh/article/details/8564319 POSIX定义了一系列同步对象用于同步和互斥.同步对象是内存中的变量属于进程中的资源,可以按照与访问数据完全相同的方式对其进行访问.默认情况下POSIX定义的这些同步对象具有进程可见性,即同步对象只对定义它的进程可见:

Linux内核同步机制--自旋锁【转】

本文转载自:http://www.cppblog.com/aaxron/archive/2013/04/12/199386.html 自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名. 由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁. 信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进

多线程中的锁系统(四)-谈谈自旋锁

目录 一:基础 二:自旋锁示例 三:SpinLock 四:继续SpinLock 五:总结 一:基础 内核锁:基于内核对象构造的锁机制,就是通常说的内核构造模式.用户模式构造和内核模式构造 优点:cpu利用最大化.它发现资源被锁住,请求就排队等候.线程切换到别处干活,直到接受到可用信号,线程再切回来继续处理请求. 缺点:托管代码->用户模式代码->内核代码损耗.线程上下文切换损耗. 在锁的时间比较短时,系统频繁忙于休眠.切换,是个很大的性能损耗. 自旋锁:原子操作+自循环.通常说的用户构造模式.

SpinLock 自旋锁, CAS操作(Compare &amp; Set) ABA Problem

SpinLock 自旋锁 spinlock 用于CPU同步, 它的实现是基于CPU锁定数据总线的指令. 当某个CPU锁住数据总线后, 它读一个内存单元(spinlock_t)来判断这个spinlock 是否已经被别的CPU锁住. 如果否, 它写进一个特定值, 表示锁定成功, 然后返回. 如果是, 它会重复以上操作直到成功, 或者spin次数超过一个设定值. 锁定数据总线的指令只能保证一个机器指令内, CPU独占数据总线. 单CPU当然能用spinlock, 但实现上无需锁定数据总线. spinl

本地自旋锁与信号量/多服务台自旋队列-spin wait风格的信号量

周日傍晚,我去家附近的超市(...)买苏打水,准备自制青柠苏打.我感觉我做的比买的那个巴黎水要更爽口.由于天气太热,非常多人都去超市避暑去了,超市也不撵人,这仿佛是他们的策略.人过来避暑了,走的时候难免要买些东西的.就跟非常多美女在公交地铁上看淘宝消磨时光,然后就下单了...这是多么easy一件事,反之开车的美女网购就少非常多.对于超市的避暑者,要比公交车上下单更麻烦些,由于有一个成本问题,这就是排队成本.       其实这是一个典型的多服务台排队问题,可是超市处理的并不好.存在队头拥塞问题.