【多线程进阶一】常见的锁策略

news2024/11/29 4:39:29

目录

一、常见的锁策略

 🍅1、常见的锁策略

 🍅2、Synchronized实现了哪些锁策略?

 🍅3、自旋锁的实现方式—CAS

(1)CAS伪代码

(2)演示 使用CAS方式来实现自增操作:

(3)分析上述自增的getAndIncrement函数过程

(4)CAS实现自旋锁

(5)CAS的ABA问题(面试题)

二、Synchronized原理

🍅1、synchronized在不同时期的锁策略

🍅2、代码演示不同时期的锁策略

🍅3、锁消除与锁粗化


         进阶内容存在很多八股内容,工作中不常用,但是面试常考。

一、常见的锁策略

        🍅1、常见的锁策略

 1、乐观锁 && 悲观锁

        乐观锁:对运行环境持有乐观态度,刚开始的时候不加锁,等有竞争的时候再去加锁;

        悲观锁:对运行环境持有悲观态度,刚开始就加锁。

 2、轻量级锁 && 重量级锁

        区分两者主要看在实现锁的过程中,消耗的资源多不多

        轻量级锁:纯用户态的锁,消耗的资源比较少;

        重量级锁:可能会调用到系统的内核态,消耗的资源比较多。

3、读写锁 && 普通互斥锁

     在现实中并不是所有的锁都要互斥,互斥必然会消耗掉很多的资源,所以优化出读写锁。

        读锁:是共享锁,读和读可以同时进行拿到锁资源;

        写锁:是排它锁,不能同时写写,写读,读写。

        普通互斥锁:synchronized,只能一个线程拿到锁资源,其他的要参与锁竞争,当没有竞争到锁的时候就要阻塞等待。

4、自旋锁 && 挂起等待锁

        自旋锁:不停的询问资源是否释放,如果释放了第一时间可以获取到锁资源;

        挂起等待锁:等待通知之后再去竞争锁,并不会第一时间获取到锁资源。

5、可重入锁 && 不可重入锁

        可重入锁:对于同一个锁对象可以加多次锁;

        不可重入锁:不能对同一个对象加多次锁。

6、公平锁 && 非公平锁

        公平锁:先排队等待的线程先获取到锁资源

        非公平锁:谁先抢到锁资源就是谁的,没有先来后到这一说。

 🍅2、Synchronized实现了哪些锁策略?

乐观锁✅&悲观锁✅
轻量级锁✅&重量级锁✅
读写锁&普通互斥锁✅
自旋锁✅&挂起等待锁✅
可重入锁✅&不可重入锁
公平锁&非公平锁 ✅

注意:其中轻量级锁是基于自旋锁实现的,重量级锁是基于挂起等待锁实现的。

自旋锁是基于CAS实现的。              

  🍅3、自旋锁的实现方式—CAS

        CAS:Compare And Swap(比较并交换)

(1)CAS伪代码

  一个 CAS 涉及到以下操作:

        1. 比较 A 与 V 是否相等。(比较)

        2. 如果比较相等,将 B 写入 V。(交换)

        3. 返回操作是否成功。

      CAS操作:去LOAD一个内存地址中的值,然后跟我们的期望值去比较宁并赋值的过程, 可以实现一个原子类。CAS操作将LOAD,CMP,STORE打包成一个指令,不需要再加锁了。

(2)演示 使用CAS方式来实现自增操作:

public static void main(String[] args) throws InterruptedException {
        //1、直接调用JDK中定义好的,原子整型
        AtomicInteger atomicInteger = new AtomicInteger();
        //2、变量1自增
        Thread t1 =new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                //3、自增操作;通过getAndIncrement()方式,而不是直接++;
                atomicInteger.getAndIncrement();
            }
        });
        t1.start();
        //变量2自增
        Thread t2 =new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                //自增操作;通过getAndIncrement()方式,而不是直接++;
                atomicInteger.getAndIncrement();
            }
        });
        t2.start();
        //4、等待两个线程执行完成
        t1.join();
        t2.join();
        //5、打印结果
        System.out.println(atomicInteger.get());
    }

 (3)分析上述自增的getAndIncrement函数过程

 上述自增过程图解:

 

         可以看出,两个线程同CAS同时对一个共享变量做自增操作,通过不停的自旋检查预期值就可以保证线程安全,没有通过加锁就可以实现正确的自增。whilie循环是在应用层执行的,也就是用户态中执行,所以比内核态的锁效率要高很多。其中,cmpxchg是CPU中的一条指令,可以完成CAS的整个操作(比较并交换),是从硬件层面上支持了原子性。

(4)CAS实现自旋锁

 (5)CAS的ABA问题(面试题)

        A B A分别表示预期值的三个状态。如果CAS出现ABA问题,可能会造成的影响。

🌰 场景:

        (1)A和B约定好今天要给C转账1000元;

        (2)如果晚上10.00前A太忙了还没来得及转账,那就由B来转账;

        (3)下午6.00,A账户当前一共有2k,转账过去1k后只剩下1k;

        (4)下午7.00,A账户收到来自公司的一笔加班费1k,此时账户一共2k;

        (5)等到晚上10.00,B检查了一下A的账户,发现还是2k,以为没有转账,又从自己的账户发起了1k的转账给C。

        (6)最终的结果就是C中多了一笔钱。

        可以看出,A的账户中的虽然是2k,但是已经不是原来的同一个值了。两个A 虽然在校验的时候可以通过,但是中途经过了修改或者其他操作,最后已经不是同一个值了。

 解决ABA问题:

        给预期值加一个版本号,在做CAS操作的时候同时更新预期值的版本号,版本号只增不减。

预期值  A  B  A

版本号 1   2   3


关于CAS:

        (1)先获取预期值

        (2)通过CAS指定完成比较并交换

        (3)如果在CAS的过程中,预期值与真实值不相等,就进入自旋操作;

        (4)如果出现ABA问题,就是给预期值加一个版本号,在比较的时候同时比较预期值和版本号。


二、Synchronized原理

🍅1、synchronized在不同时期的锁策略

        synchronized在不同的时期可能会用到不同的锁策略。             

 🌰 场景:

(1)一栋正在新建的大楼:每一层都有一个装修师傅,有一个卫生间。那么每一层的装修师傅去卫生间的时候可以在卫生间门口贴一个标签,但是并没有真正的上锁。因为不存在锁竞争,这个贴标签的过程可以理解为是偏向锁。

(2)随着任务越来越重,每一层现在有多了一个装修师傅,那么这个时候两个人需要竞争,演化为轻量级锁;

(3)随着大楼建设好,打工人开始入驻,此时每一层的卫生间的竞争压力越来越大,就需要排队等待资源,演化为重量级锁。

🍅2、代码演示不同时期的锁策略

         大家可以自己试着下面的代码跑一下,观察一下不同状态下的锁状态,可以有更深的理解。           在pom.xml中加入以下代码:

    <dependencies>
        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.16</version>
        </dependency>
    </dependencies>
public class a02_synchronizedLock {
    //通过这个类可以查看锁对象的对象头中的信息。
    // 定义一些变量
    private int count;
    private long count1 = 200;
    private String hello = "";
    // 定义一个对象变量
    private TestLayout test001 = new TestLayout();

    public static void main(String[] args) throws InterruptedException {
        // 创建一个对象的实例
        Object obj = new Object();
        // 打印实例布局
        System.out.println("=== 任意Object对象布局,起初为无锁状态");
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());

        System.out.println("=== 延时4S开启偏向锁");
        // 延时4S开启偏向锁
        Thread.sleep(5000);
        // 创建本类的实例
        a02_synchronizedLock monitor = new a02_synchronizedLock();
        // 打印实例布局,注意查看锁状态为偏向锁
        System.out.println("=== 打印实例布局,注意查看锁状态为偏向锁");
        System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

        System.out.println("==== synchronized加锁");
        // 加锁后观察加锁信息
        synchronized (monitor) {
            System.out.println("==== 第一层synchronized加锁后");
            System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
            // 锁重入,查看锁信息
            synchronized (monitor) {
                System.out.println("==== 第二层synchronized加锁后,锁重入");
                System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
            }
            // 释放里层的锁
            System.out.println("==== 释放内层锁后");
            System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
        }
        // 释放所有锁之后
        System.out.println("==== 释放 所有锁");
        System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

        System.out.println("==== 多个线程参与锁竞争,观察锁状态");
        Thread thread1 = new Thread(() -> {
            synchronized (monitor) {
                System.out.println("=== 在线程A 中获取锁,参与锁竞争,当前只有线程A 竞争锁,轻度锁竞争");
                System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
            }
        });
        thread1.start();

        // 休眠一会,不与线程A 激烈竞争
        Thread.sleep(100);
        Thread thread2 = new Thread(() -> {
            synchronized (monitor) {
                System.out.println("=== 在线程B 中获取锁,与其他线程进行锁竞争");
                System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
            }
        });
        thread2.start();

        // 不休眠直接竞争锁,产生激烈竞争
        System.out.println("==== 不休眠直接竞争锁,产生激烈竞争");
        synchronized (monitor) {
            // 加锁后的类对象
            System.out.println("==== 与线程B 产生激烈的锁竞争,观察锁状态为fat lock");
            System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
        }
        // 休眠一会释放锁后
        Thread.sleep(100);
        System.out.println("==== 释放锁后");
        System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

        System.out.println("===========================================================================================");
        System.out.println("===========================================================================================");
        System.out.println("===========================================================================================");
        System.out.println("===========================================================================================");
        System.out.println("===========================================================================================");
        System.out.println("===========================================================================================");

        // 调用hashCode后才保存hashCode的值
        monitor.hashCode();
        // 调用hashCode后观察现象
        System.out.println("==== 调用hashCode后查看hashCode的值");
        System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
        // 强制执行垃圾回收
        System.gc();
        // 观察GC计数
        System.out.println("==== 调用GC后查看age的值");
        System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
        // 打印类布局,注意调用的方法不同
        System.out.println("==== 查看类布局");
        System.out.println(ClassLayout.parseClass(a02_synchronizedLock.class).toPrintable());
        // 打印类对象布局
        System.out.println("==== 查看类对象布局");
        System.out.println(ClassLayout.parseInstance(a02_synchronizedLock.class).toPrintable());

    }
}
class TestLayout {

}

🍅3、锁消除与锁粗化

1、锁消除            

        在写代码的时候,程序员会加synchronized来保证线程的安全。如果加了synchronized的代码块中,只有读操作,没有写操作,那么JVM就会认为这个代码没有必要加锁,JVM在运行的时候就会被优化掉,这个现象就叫做锁消除。也就是说过滤到无效的synchronized来提高效率。锁消除的前提是:JVM只有100%的把握的时候才会进行优化。

2、锁粗化

        代码有一连串的方法调用,方法1-4都加了synchronized,代码执行的时候是先执行方法1,出了方法1继续执行方法2,依次类推。在这个过程中执行一个业务逻辑要进行4次锁竞争。因此,在保证程序执行真确的前提下,JVM会做出优化,只加一次锁,整个逻辑执行完之后再释放。


加油~~ 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux 系统修改环境变量的方法

1. Linux 系统修改环境变量 正常情况下改变环境变量可以修改的文件有两类&#xff1a; 第一类是 “系统的全局环境变量”&#xff0c;修改之后可作用于整个系统包含的所有用户都会生效&#xff1b;(文件&#xff1a;/etc/profile) 第二类是 “局部环境变量”&#xff0c;也就…

Linux C程序多文件编译

C程序多文件编译 在Linux平台C编程&#xff0c;实现求两数最大值和两数之和的功能 1.编写add.c wysDESKTOP-2OU3HRV:~/mycode/day02$ vi add.c1 int add(int x,int y)2 {3 return x y;4 } 2.编写头文件add.h wysDESKTOP-2OU3HRV:~/mycode/day02$ vi add.hint add(…

Python 实验五 字符串与正则表达式

1.输入一个字符串&#xff0c;将该字符串中下标为偶数的字符组成新串并通过字符串格式化方式显示。 a input("请输入一个字符串&#xff1a;") b a[1::2] print("老串为&#xff1a;%a&#xff0c;新串为&#xff1a;%a"%(a,b))2.编写程序&#xff0c;生…

LInux系统下使用git的三板斧以及报错处理

LInux使用git 我们应该知道git是什么东西&#xff0c;还有git的三板斧&#xff0c;git是一个工具&#xff0c;使用git来将文件上传到代码仓库 文章目录 LInux使用gitcloneaddcommitpush查看当前git的状态 clone 第一步找到你创建的仓库&#xff0c;然后复制http地址&#xf…

目前可以用的ChatGPT网址大全

ChatGPT是一个基于人工智能的聊天机器人&#xff0c;可以与用户进行自然语言交互。它可以回答各种问题&#xff0c;提供有用的信息和建议&#xff0c;还可以进行闲聊和娱乐。ChatGPT使用最先进的自然语言处理技术&#xff0c;可以理解和解释人类语言&#xff0c;从而提供准确和…

【Linux】IO多路转接 - epoll

文章目录 I/O多路转接之epollepoll初识epoll的相关系统调用函数epoll_createepoll_ctlepoll_wait epoll工作原理epoll服务器-*epoll的优缺点epoll工作方式对比LT和ET I/O多路转接之epoll epoll初识 epoll也是系统提供的一个多路转接的接口,epoll才是使用和面试的重点,在效率和…

SpringBoot【开发实用篇】---- 整合第三方技术(缓存)

SpringBoot【开发实用篇】---- 整合第三方技术&#xff08;缓存&#xff09; SpringBoot内置缓存解决方案手机验证码案例SpringBoot整合Ehcache缓存SpringBoot整合Redis缓存SpringBoot整合Memcached缓存SpringBoot整合jetcache缓存纯远程方案纯本地方案本地远程方案远程方案的数…

tomcat控制台打印乱码解决

一、注册表修改 HKEY_CURRENT_USER ->console ->tomcate 新增 32位 CodePage 16进制 fde9 二、idea 中配置 Tomcat 后启动服务&#xff0c;输出打印日志乱码问题 解决办法&#xff1a; ①、打开安装idea文件路径&#xff0c;在bin目录下&#xff0c;找到下面两个文件 ②…

图像动态裁剪

1. 背景 以两级级联模型为例&#xff0c;第一级目标检测模型用于检测人员&#xff0c;第二级目标检测模型用于检测手机、对讲机等。然后实际数据采集过程中&#xff0c;手机、对讲机这些设备并不在人员的一级检测框内&#xff0c;使得二级模型训练的样本较少。 二级目标检测模…

详细讲解,接口自动化—Requests之Cookie鉴权关联接口实战

目录 前言&#xff1a; 一、 简介 二、 实战操作 1. 登录接口 2. 查询订单接口 3. 新增订单接口 4. 修改订单接口 5. 删除订单接口 三、 结束语 前言&#xff1a; 接口自动化测试是软件测试过程中的重要一环&#xff0c;现在越来越多的公司开始使用自动化测试来提高测…

Gigabyte Z490 Vision D i9-10900k电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。&#xff08;下载请直接百度黑果魏叔&#xff09; 硬件型号驱动情况 主板Gigabyte Z490 Vision D 处理器Intel i9-10900k已驱动 内存64GB G.Skill Trident Z 3600Mhz CL18已驱动 硬盘西数 WDS250G3X0C-00SJG0 ( SN750) …

cad文件怎么转换成pdf格式?一键操作的4个方法

在很多时候&#xff0c;我们为了能够更好地查看CAD图纸&#xff0c;需要将其格式转换为PDF。所以说&#xff0c;CAD文件格式的转换是非常关键的。首先&#xff0c;将CAD转换为PDF格式能够有效提升文件的兼容性。CAD软件通常需要特定的软件才能打开和编辑&#xff0c;而PDF格式则…

Python Scrapy爬虫框架安装和创建

1、检查Win环境 python版本 python 2、whl方式安装 twisted twisted异步网络框架&#xff0c;可加快下载速度。优点是用少量的代码实现快速的抓取。 由于scrapy需要twisted的环境&#xff0c;我们直接去下载whl文件根据自己的Python版本选择 https://www.lfd.uci.edu/~gohlke/p…

由浅入深理解java集合(五)——集合 Map

HashMap 前面已经介绍完了Collection接口下的集合实现类&#xff0c;今天我们来介绍Map接口下的两个重要的集合实现类HashMap,TreeMap。 HashMap 是一个散列表&#xff0c;它存储的内容是键值对(key-value)映射。 既然要介绍HashMap&#xff0c;那么就顺带介绍HashTable,两者进…

【UE4】部署像素流

目录 一、单实例本地像素流送 步骤 1. 勾选插件 2. 打包工程并启动信令服务器 3. 创建快捷方式并启动游戏 二、单实例局域网像素流送 步骤 1. 编辑cirrus.js 2. 编辑快捷方式属性 3. 启动 一、单实例本地像素流送 步骤 1. 勾选插件 勾选使用“Pixel Streaming”插件&…

瑞吉外卖 - 新增员工功能(6)

某马瑞吉外卖单体架构项目完整开发文档&#xff0c;基于 Spring Boot 2.7.11 JDK 11。预计 5 月 20 日前更新完成&#xff0c;有需要的胖友记得一键三连&#xff0c;关注主页 “瑞吉外卖” 专栏获取最新文章。 相关资料&#xff1a;https://pan.baidu.com/s/1rO1Vytcp67mcw-PD…

智慧水务管控一体化平台,实现水务数字化管理

平台概述 柳林智慧水务管控一体化平台是以物联感知技术、大数据、智能控制、云计算、人工智能、数字孪生、AI算法、虚拟现实技术为核心&#xff0c;以监测仪表、通讯网络、数据库系统、数据中台、模型软件、前台展示、智慧运维等产品体系为支撑&#xff0c;以城市水资源、水生…

ArcSWAT报错:数据集未投影;Dataset must have a projected coordinate system

文章目录 1 报错内容2 定义投影3 重新执行ArcSWAT相关步骤 1 报错内容 Dataset must have a projected coordinate system. The current coordinate system is geographic . Please define a projected coordinate system for your DEM dataset using ArcToolbox before procee…

Java 线程池(Thread Pools)详解

目录 1、线程池介绍 2、线程池执行原理 3、线程池中的阻塞队列 4、Java 线程池中的拒绝策略 5、Java 提供的创建线程池的方式 6、线程池的使用示例 7、ForkJoinPool 和 ThreadPool 的区别 1、线程池介绍 线程池是一种重用线程的机制&#xff0c;用于提高线程的利用率和管…

Android开发:我们很迷茫,出路在哪里?

“都说今年是互联网行业寒风刺骨&#xff0c;尤其移动端开发市场更是饱和&#xff0c;在跌跌撞撞近一个月后&#xff0c;我终于在一家小公司找到了工作。入职后&#xff0c;领导让我接手一个二手Android项目&#xff0c;项目很庞大&#xff0c;前任开发人员已离职一个多月了&am…