CAS-手写自旋锁

CAS与自旋锁,借鉴CAS思想

什么是自旋锁?

CAS是实现自旋锁的基础,CAS利用CPU指令保证了操作的原子性,以达到锁的效果,至于自旋

锁---字面意思自己旋转。是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取

锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文

切换的消耗,缺点是循环会消耗CPU。

底层是do...while循环:

扩展1.1:

理解自旋锁

自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程

将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-

waiting,也就是线程空转。自旋锁优点是所有线程都是“运行”状态,不需要唤醒线程,速度较快;

不足则是线程的空转导致无谓的资源损耗。针对这类问题也有相关的等待策略进行优化,应对各种

场景,合理利用资源。 

扩展1.2:

所谓自旋锁就是通过while循环实现的,让拿到锁的线程进入临界区执行代码,让没有拿到锁的线

程一直进行while死循环,这其实就是线程自己“旋”在while循环了,因而这种锁就叫做自旋锁。

扩展1.3: 

自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式取尝试获取锁

  • 这样做的好处是减少线程上下文切换的消耗
  • 缺点是循环会消耗CPU。
  • 循环比较获取,直到成功为止,没有类似wait的阻塞。

扩展2:

独占锁是一种悲观锁,synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线

程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而

另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操

作,如果因为冲突失败就重试,直到成功为止

这种乐观的锁叫做无锁,与加锁而言对临界区域是无障碍,通过CAS算法(用多个线程尝试使用

CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程

并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试)。CAS操作CPU的指令的操作,

只有一步原子操作,必须要考线程安全的。


自己实现一个自旋锁spinLockDemo

题目:实现一个自旋锁,借鉴CAS思想

通过CAS完成自旋锁,A线程先进来调用lock方法自己持有锁5秒钟,B随后进来后发现当前有

线程持有锁,所以只能通过自旋等待,直到A释放锁后B随后抢到。

public class SpinLockDemo {AtomicReference<Thread> atomicReference = new AtomicReference<>();public void lock() {Thread thread = Thread.currentThread();System.out.println(Thread.currentThread().getName() + "\t --------come in");//准备抢占while (!atomicReference.compareAndSet(null, thread)) {//空轮询-> 直到atomicReference为null时,执行CAS,并跳出循环}}public void unLock() {Thread thread = Thread.currentThread();atomicReference.compareAndSet(thread, null);System.out.println(Thread.currentThread().getName() + "\t --------task over,unLock.........");}public static void main(String[] args) {SpinLockDemo spinLockDemo = new SpinLockDemo();new Thread(() -> {spinLockDemo.lock();try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}spinLockDemo.unLock();}, "A").start();try {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {spinLockDemo.lock();spinLockDemo.unLock();}, "B").start();}
}/*** A	 --------come in* B	 --------come in* A	 --------task over,unLock.........* B	 --------task over,unLock.........*///也可以这样new线程
public static void main(String[] args) {SpinLockDemo spinLockDemo = new SpinLockDemo();Thread t1 = new Thread(() -> {spinLockDemo.lock();try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}spinLockDemo.unLock();},"A");t1.start();try {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}Thread t2 = new Thread(() -> {spinLockDemo.lock();spinLockDemo.unLock();}, "B");t2.start();
}

扩展3:

线程安全之CAS机制详解 

背景介绍:假设现在有一个线程共享的变量c=0,让两个线程分别对c进行c++操作100次,那么我们最后得到的结果是200吗?1.在线程不安全的方式下:结果可能小于200,比如当前线程A取得c的值为3,然后线程A阻塞了,线程B取得的c的值也是3,然后线程B也阻塞了,现在线程A被唤醒执行了++操作使得c=4,结果写回c值内存,线程A执行结束,线程B被唤醒执行了++操作使得3++=4,也写回了c值内存,现在问题来了,两个线程分别进行了一次++操作,最后c值却为4而不是5,所以c值最后的结果肯定是小于200的,产生这种情况的原因就是线程不安全!,两个线程在同一时间读取了c值,然后又没有各种先执行完++操作而被阻塞(就是没有同步)2.在线程安全的方式下:比如++操作加上synchronized同步锁,结果一定是200,因为这样使得读取c值和++操作是一个原子性操作,不能被打断,所以线程是安全的,保证了同步现在问题来了,我们要保证线程安全只有加synchorized同步锁这一种办法吗?synchorized同步锁又有什么缺点呢?当然不仅只有synchorized这一种方法,还有原子操作类,关于原子操作类我们等下再说,先说说synchorized的缺点:syschorized缺点:synchorized的缺点关键在于性能!我们知道synchorized关键字会让没有得到锁资源的线程进入Blocked状态,而在得到锁的资源恢复为Runnable状态,这个过程涉及到操作系统用户模式和内核模式的切换,代价比较高!现在我们来说说原子操作类,顾名思义,就是保证某个操作的原子性,那它是怎么实现的呢?这个我们就要垃圾原子操作类的底层:CAS机制了CAS机制的英文缩写是Compare and Swap,翻译一下就是比较和交换CAS机制中使用3个基本操作数:内存地址V,旧的预期值A,要修改的新值B,更新一个变量的时候,只有当变量的旧的预期值A和内存地址V中的值相同的时候,才会将内存地址V中的值更新为新值B下面举个栗子:1)内存地址V中存放着值为10的变量2)此时线程1要把变量值加1,对线程1来说,旧的预期值A=10,要修改的新值B=113)在线程1提交更新之前,另外一个线程2提前一步将内存地址V中的变量值率先更新成了114)线程1此时开始提交更新,首先进行A和内存地址V中的值比较,发现A不等于此时内存地址V中的值11,提交失败5)线程1尝试重新获取内存地址V的当前值,并重新计算想要修改的值,对线程1来说,此时旧的预期值A=11,要修改的新值B=12,这个重新尝试的过程叫做自旋6)这一次比较幸运,没有其他线程更改内存地址V中的值,线程1进行compare,发现A和内存地址V中的值相同7)线程1进行Swap,把内存地址V中的值替换为B,也就是12这个过程涉及到以下几个问题:问题1:如何保证获取的当前值是内存中的最新值?(如果每次获得的当前值不是内存中的最新值,那么CAS机制将毫无意义)用volatile关键字修饰变量,使得每次对变量的修改操作完成后一定会先写回内存,保证了每次获取到值都是内存中的最新值!问题2:如何保证Compare和Swap过程中的原子性(如果Compare和Swap过程不是原子性操作,那么CAS机制也毫无意义)?Compare和Swap过程的原子性是通过unsafe类来实现的,unsafe类为我们提供了硬件级别的原子操作!总结一下:从思想上来说,Synchorized属于悲观锁,悲观的认为程序中的并发多,所以严防死守,CAS机制属于乐观锁,乐观的认为程序中并发少,让线程不断的去尝试更新那么现在又有一个问题来了,CAS机制有什么缺点呢?CAS机制的缺点:1.CPU开销过大:在并发量比较高的情况下,如果许多线程反复尝试去更新一个变量,却又一直更新失败,循环往复,会消耗CPU很多资源2.ABA问题:假设在内存中有一个值为A的变量储存在内存地址V当中,此时有三个线程使用CAS机制更新这个变量的值,每个线程的执行时间都略有偏差,线程1和线程2已经获取当前值,线程3还没有获取当前值。接下来线程1先一步执行成功,把当前值成功从A更新为B,同时线程2因为某种原因被阻塞,没有做更新操作,线程3在线程1更新成功之后获取了当前值B,再之后线程2仍然阻塞,线程3继续执行,成功将当前值更新为A,最后,线程2终于恢复了运行状态,由于线程2之前获取了“当前值A”并且经过了Compare检测,内存地址中的实际值也是A,所以线程2最后把变量A更新成了B,在这个过程中,线程2获取的当前值是一个旧值,尽管和当前值一模一样,但是内存地址中V中的变量已经经历了A->B->A的改变表面看没有什么影响,但是如果实际中理由CAS机制从取款机上取钱,假如账户开始有100元,在取款机上取走50,取款机出现问题一共提交了两次请求(线程1,线程2),第二次请求(线程2)在执行时因为某种原因被阻塞了,这时候有人往你的账户打了50元,线程2恢复了可执行状态,这个时候就会出现问题,原本线程2应该执行失败的,但是比较后仍然与旧值一致,这样就造成了账户实际上扣款了两次!ABA问题解决的方案:在Compare阶段不仅比较预期值和此时内存中的值,还比较两个比较变量的版本号是否一致,只有当版本号一致才进行后续操作,这样就完美的解决了ABA问题!3.不能保证代码块的原子性:CAS机制保证的是一个变量的原子性操作,若要保证多个变量的原子性操作,可以封装在一起,但是这样得不偿失,开销太大,还不如直接采用synchorized同步锁

扩展4:自旋锁Java实现

浅谈自旋锁的Java实现 - 知乎

从零开始自己动手写自旋锁 - 知乎

自旋锁的介绍,手写一个简单的自旋锁

并发开篇——带你从0到1建立并发知识体系的基石

【Java锁】(公平锁、非公平锁、可重入锁、递归锁、自旋锁)谈谈你的理解?手写一个自旋锁 - 简书

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/305825.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

最新云渲染平台选择,云渲染避免踩坑指南

​随着云计算技术的飞速发展&#xff0c;云渲染逐步成为各类行业的优选工具&#xff0c;它凭借出色的效能和强大的并行计算能力&#xff0c;显著提高了工作效率&#xff0c;然而&#xff0c;要想充分利用云渲染技术&#xff0c;选择一款合适的云渲染平台是刻不容缓的课题&#…

【C++进阶03】二叉搜索树

一、二叉搜索树的概念和性质 中序遍历二叉搜索树会得到一个有序序列 所以二叉搜索树又称二叉排序树 它可以是一棵空树 也可以是具有以下性质的二叉树&#xff1a; 若它的左子树不为空 则左子树上所有节点的值都小于根节点的值若它的右子树不为空 则右子树上所有节点的值都大于…

PO设计模式详解:从入门到精通一文解读

PO模式&#xff1a; 全称&#xff1a;page objece&#xff0c;分层机制&#xff0c;让不同层去做不同类型的事情&#xff0c;让代码结构清晰&#xff0c;增加复⽤性。 PO模式的优势&#xff1a; 1&#xff09;效率⾼ &#xff1a;同理&#xff0c;PO模式的逻辑层⽅法有具体定…

算法时间空间复杂度计算—空间复杂度

算法时间空间复杂度计算—空间复杂度 空间复杂度定义影响空间复杂度的因素算法在运行过程中临时占用的存储空间讲解 计算方法例子1、空间算法的常数阶2、空间算法的线性阶&#xff08;递归算法&#xff09;3、二分查找分析方法一&#xff08;迭代法&#xff09;方法二&#xff…

干货!一文详解车间管理的五大基本方法

车间管理是制造型企业生产过程中的重要环节&#xff0c;它直接影响着企业的生产效率、成本控制、产品质量以及员工的士气与工作效率。优秀的车间管理不仅能够提升产品的质量和生产力&#xff0c;还能降低运营成本&#xff0c;从而在激烈的市场竞争中为企业赢得优势。 为了帮助…

Rhinos各版本安装指南

下载链接 https://pan.baidu.com/s/1L5qeUPMW32d7zR-GlVVZIw?pwd0531 温馨提示&#xff1a;若您下载的安装包与该安装步骤不同&#xff0c;说明您使用的是之前被淘汰的安装包&#xff0c;请通过该页面的下载链接重新下载。 1.鼠标右击【Rhino8.1(64bit)】压缩包&#xff08…

Vue(一):Vue 入门与 Vue 指令

Vue 01. Vue 快速上手 1.1 Vue 的基本概念 用于 构建用户界面 的 渐进性 框架 构建用户界面&#xff1a;基于数据去渲染用户看到的界面渐进式&#xff1a;不需要学习全部的语法就能完成一些功能&#xff0c;学习是循序渐进的框架&#xff1a;一套完整的项目解决方案&#x…

Spring AOP—深入动态代理 万字详解(通俗易懂)

目录 一、前言 二、动态代理快速入门 1.为什么需要动态代理&#xff1f; : 2.动态代理使用案例&#xff1a; 3.动态代理的灵活性 : 三、深入动态代理 1.需求 : 2.实现 : 2.1 接口和实现类 2.2 提供代理对象的类 2.3 测试类 3.引出AOP : 四、总结 一、前言 第四节内容&…

设备不锈钢二维码标识牌

随着科技的不断发展&#xff0c;二维码已经应用于身边的各个领域。特别是在建筑施工、工业制造、设备管理等领域被广泛应用。 不锈钢二维码的优势。首先&#xff0c;不锈钢材质具有高度耐腐蚀、抗压和耐磨损的特点&#xff0c;可以适应各种极端环境。其次&#xff0c;不锈钢二…

mysql保姆安装教程

一.下载install文件 1.进入Mysql官网&#xff0c;点击下载 2.选择MySQL Installer for Windows 3.推荐选择第二个安装包 4.不登陆&#xff0c;开始下载 5.等待下载完成 二.安装前的配置 通过电脑“设置”&#xff0c;检查电脑是否包含中文名&#xff0c;如果包含请重命名 …

Python中JSON模块的使用

1 JSON简介 JSON是JavaScript Object Notation即Javascript对象简谱的缩写。JSON是一种轻量级的数据交换格式&#xff0c;JSON数据是由键值对组成的结构&#xff0c;与Python中的字典类似&#xff0c;由尖括号包围的键值对组成&#xff0c;键和值的类型可以是字符串、数字、布…

drf知识-08

Django之了解DRF框架 # 介绍&#xff1a;DRF全称 django rest framework # 背景&#xff1a; 在序列化与反序列化时&#xff0c;虽然操作的数据不尽相同&#xff0c;但是执行的过程却是相似的&#xff0c;也就是说这部分代码是可以复用简化编写的 增&#xff1a;校验请…