常见锁策略

目录

乐观锁和悲观锁

重量级锁和轻量级锁

自旋锁和挂起等待锁

互斥锁和读写锁

公平锁和非公平锁

可重入锁和不可重入锁

synchronized内部的工作原理

锁消除

锁粗化

CAS


锁策略,即加锁过程(处理冲突时)时的处理方式

乐观锁和悲观锁

乐观锁:在加锁之前,预估当前出现锁冲突的概率不大,因此在进行加锁的时候不会做太多工作。加锁过程需要做的事情少,则加锁的速度可能更快,但更容易出现一些其他问题(消耗更多的cpu资源)

悲观锁:在加锁之前,预估当前出现锁冲突的概率较大,因此在进行加锁的时候会做更多的工作。加锁过程需要做的事情多,则加锁速度可能更慢,但过程中不容易出现其他问题

重量级锁和轻量级锁

轻量级锁:加锁的开销小,加锁的速度更快(轻量级锁,一般就是乐观锁)

重量级锁:加锁的开销大,加锁的速度更慢(重量级锁,一般就是悲观锁)

轻量还是重量,是加锁之后对结果的评价,而乐观还是悲观是还未加锁前,对锁冲突的预估,但从两种角度都是描述的同一件事情

自旋锁和挂起等待锁

自旋锁:在进行加锁的时候,如果获取锁失败,就立即再尝试获取锁,无限循环,直到获取到锁为止(如:while ( 获取锁 == 失败 ) {})一旦锁被其他线程释放,就能够第一时间获取到锁

自旋锁是一种典型的 轻量级锁 的实现方式,也是一种乐观锁,适用于锁冲突不激烈的情况

优点:不放弃CPU,不涉及线程阻塞和调度,一旦锁被释放,就能够第一时间获取到锁

缺点:若锁一直不被其他线程释放,则会持续消耗CPU资源

挂起等待锁:在进行加锁的时候,如果获取锁失败,就挂起等待(不消耗CPU资源),而当其他线程释放锁,由系统决定是否其进行加锁

挂起等待锁是一种典型的 重量级锁 的实现方式,也是一种悲观锁,适用于锁冲突激烈的情况

优点:减少了CPU资源的浪费

缺点:锁被释放后,不能第一时间获取到锁,再次加锁时间由系统决定

synchronized是哪一种锁呢?

synchronized具有自适应能力,某些情况下是 乐观锁(轻量级锁/自旋锁),而某些情况下是 悲观锁(重量级锁/挂起等待锁)。synchronized内部会自动评估当前锁冲突的激烈程度,若当前锁冲突激烈程度不大,就是 乐观锁(轻量级锁/自旋锁);而若当前锁冲突较为激烈,则是悲观锁(重量级锁/挂起等待锁) 

互斥锁和读写锁

互斥锁:加锁就是单纯的加锁,即资源只能当前被获取锁的线程访问

加锁操作是为了防止 一个线程在进行写操作时,另一个线程进行读取或者写操作

而,若线程都只对其进行读操作,此时不会涉及到线程安全问题,此时加上互斥锁就会影响读取速度,对性能有一定的损失,此时,我们可以使用读写锁解决上述问题

读写锁:

当一个线程加读锁时,另一个线程只能读,不能写,即当前线程正在进行读取操作,其他线程也可以进行读取操作,但是不能写

当一个线程加写锁时,另一个线程不能读,也不能写,即当前线程正在进行写操作,其他线程即不行读也不能写

synchronized属于互斥锁,其操作只涉及到加锁和写锁。在标准库中,提供了专门的读写锁(ReadWriteLock

公平锁和非公平锁

公平锁:遵循“先来后到”原则,即等待时间长的线程先获取到锁

非公平锁:不遵循“先来后到”原则,线程等概率获取到锁

在操作系统内部的线程调度可视为随机的,即锁是非公平锁,若想实现公平锁,就需要使用额外的数据结构来记录线程之间的先后顺序。synchronized 是非公平锁

可重入锁和不可重入锁

可重入锁:可重新进入的锁,即允许同一个线程多次获得到同一把锁

不可重入锁:不可重新进入的锁,即不允许同一个线程多次获取到同一把锁

例如:一个递归函数中有加锁操作,在递归过程中这个锁会阻塞自己吗?若不会,则是可重入锁;若会,则是不可重入锁(即自己把自己锁死)

在Java中,只要以Reentrant开头命名的锁都是可重⼊锁,⽽且JDK提供的所有现成的Lock实现类,包括synchronized关键字锁都是可重入的。而Linux系统提供的mutex是不可重入锁。

可重入锁的实现方式是在锁中记录持有该锁的线程身份,以及实现一个计数器(用于记录加锁次数),若当前加锁的线程是当前持有锁的线程,则只需要将计数器次数增加。

synchronized内部的工作原理

结合上面的锁策略我们可以看出sychronized:

1. 具有自适应能力(不同情况下使用不同锁)

2. 不是读写锁,是互斥锁

3. 是不公平锁

4.是可重入锁

synchronized的加锁过程:

JVM将synchronized分为 无锁、偏向锁、轻量级锁、重量级锁 状态。会根据情况,进行依次升序

偏向锁状态:

偏向锁并不是真的进行“加锁”,而是只给锁对象做一个“偏向锁标记”,记录这个锁属于哪个线程。若后续没有其他线程来竞争该锁,则不用进行其他操作(这样就避免了加锁和解锁的开销),而若后续有其他的线程来竞争该锁(由于已经对锁对象进行标记,因此很容易识别申请加锁的线程是否是之前记录的线程),则就进入 轻量级锁状态

偏向锁本质上相当于“延迟加锁”,即能不加锁就不加锁,尽量避免不必要的加锁开销

轻量级锁状态:

当其他线程竞争该锁的时候,偏向锁状态解除,进入轻量级锁状态(自适应的自旋锁)

每次加锁都会经历这三个阶段吗?

偏向锁标记,是对象头里的一个标记,每个锁对象都有自己的标记,当这个锁对象首次被加锁时,会先进入偏向锁状态,而在这个过程中,没有涉及锁竞争,则下次加锁时还是进入偏向锁状态;而若在此过程中升级为轻量级锁,后续再针对这个对象加锁,就直接是轻量级锁了(跳过偏向锁

重量级锁状态:

当锁竞争进一步激烈,自旋不能快速获取到锁状态,则会进入重量级锁状态

此时拿不到锁的线程就不会继续自旋了,而是进入阻塞等待,让出CPU。而当持有锁的线程释放锁时,就由系统随机唤醒一个线程来获取锁。

锁消除

编译器会判断当前锁是否可以消除,若可以消除,就直接将其消除掉

例如:

StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("ab");
stringBuilder.append("bc");
stringBuilder.append("cd");
stringBuilder.append("de");

在上述使用了synchronized,但没有多线程的环境下,若每个append的调用都进行加锁和解锁,则会浪费资源开销,降低效率,因此这些加锁和解锁操作是没有必要的,是可以进行“锁消除”的

锁粗化

若一段逻辑中多次出现加锁和解锁,则编译器会将自动将其合并为异常加锁和解锁,即将多个细粒度的锁合并为一个粗粒度的锁

使用细粒度的锁,是期望释放锁的时候其他线程能够使用锁,但实际可能没有其他线程来抢占这个锁,此时JVM就会自动将锁粗化,避免频繁加锁和解锁

CAS

CAS:compare and swap,即比较并交换,一个CAS涉及以及操作:

设内存中原有数据V,旧的预期值为A,需要修改的新值为B

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

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

3.返回操作是否成功

例如,我们以下面的伪代码来理解CAS的工作流程:

boolean CAS(address, expectValue, swapValue){
    if(&address == expectValue){
        &address = swapValue;
        return true;
    }
    return false;
}

address:内存地址,expectValue:寄存器中旧的预期值,swapValue:需要修改的新值

比较address内存地址中的值是否与expectValue相同,若相同则将swapValue与address内存中的值交换(也可以理解为“赋值”,我们往往只关注内存中最终的值,寄存器中的值用完就不需要了,),并返回true;若不相同,则不进行交换,并返回false

上述的伪代码,实际上表示的是一条CPU指令,而单个CPU指令,本身就是原子的,因此,使用CAS,不涉及加锁,也不会阻塞,合理使用也能够保证线程安全

CAS本身是CPU指令,操作系统对指令进行了封装,而JVM又对操作系统提供的api进行了一层封装。Java将CAS的api放到了unsafe包(CAS涉及到一些系统底层内容,使用不当可能会带来一定的风险)中。因此,Java标准库又对CAS进行了一层封装,提供了一些工具类

其中最主要的一个工具,叫做原子类

import java.util.concurrent.atomic

例如,AtomicInteger,对Integer进行了封装,当针对Integer对象进行多线程修改时,就是线程安全的

 当我们使用AtomicInteger在多线程情况下对Integer对象count进行++操作:

class Demo{
    private static AtomicInteger count = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                count.getAndIncrement();
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                count.getAndIncrement();
            }
        });
        t1.start();
        t2.start();
        
        t1.join();
        t2.join();
        System.out.println(count.get());
    }
}

其运行结果为10000

而当我们直接使用int类型的变量时:

class Demo1{
    private static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                count++;
            }
        });

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                count++;
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(count);
    }
}

此时的运行结果则是随机的,但基本上是小于10000的

为什么会出现不同的结果呢?

在使用count++时,涉及到三个指令 load add save 

因此,虽然执行了两次count++操作,但结果却为1,要想解决上述问题,则需要通过加锁操作,将这三个操作“打包”,即让其具有“原子性”(三个操作连在一起执行,执行过程中其他线程不能调度执行)

而当使用count.getAndIncrement()方法时,

 

 基于上图中的情况,其能够判断在执行++操作前是否有另一个线程修改了count,若count被修改,则重新获取预期值

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1panel中的sftpgo webadmin 更新修改docker容器文件的配置教程

本篇文章主要讲解1panel中的sftpgo webadmin 更新修改docker容器文件的配置教程&#xff0c;适合sftpgo webadmin和1panel系统用户配置时使用。 作者&#xff1a;任聪聪 rccblogs.com 日期&#xff1a;2024年1月8日 sftpgo是无法直接直接更改容器内部的网站目录的&#xff0c;但…

酷开科技创新玩法寻找OTT大屏营销新增长

随着技术与数据入局OTT领域&#xff0c;程序化投放、数据追踪、人群定位等等功能也正逐步深入到大屏营销&#xff0c;很大程度上推动了OTT行业的快速发展。围绕OTT大屏营销&#xff0c;品牌的机会点早已脱离了传统营销模式&#xff0c;新营销的价值正在被重构。 消费者在接触品…

小游戏选型(一):游戏化设计助力直播间互动和营收

一、社交直播间小游戏火爆 大家好&#xff0c;作为一个技术宅和游戏迷&#xff0c;今天来聊聊近期爆火的社交直播间小游戏的潮流。喜欢冲浪玩社交产品的小伙伴会发现&#xff0c;近期各大平台都推出了直播间社交小游戏&#xff0c;直播间氛围火爆&#xff0c;小游戏玩法简单&a…

市场上最受欢迎的6 大顶级安卓手机解锁软件

Android 智能手机不断发展&#xff0c;每天都有新的特性和功能加入。然而&#xff0c;密码仍然是一个大问题&#xff0c;因为我们的个人数据存储在这些设备上。因此&#xff0c;忘记密码是令人沮丧的。 不过别担心。有很多可用的 Android 解锁软件可以绕过 Android 设备的锁定…

Prompt提示工程上手指南:基础原理及实践(一)

想象一下&#xff0c;你在装饰房间。你可以选择一套标准的家具&#xff0c;这是快捷且方便的方式&#xff0c;但可能无法完全符合你的个人风格或需求。另一方面&#xff0c;你也可以选择定制家具&#xff0c;选择特定的颜色、材料和设计&#xff0c;以确保每件家具都符合你的喜…

CSS 圆形分割按钮动画 带背景、图片

<template><view class="main"><view class="up"> <!-- 主要部分上 --><button class="card1"><image class="imgA" src="../../static/A.png"></image></button><butt…

基于机器视觉的车牌检测-字符识别

一般步骤 字符识别常用的有以下四类&#xff1a; 第一类&#xff1a;结构识别方法。 第二类&#xff1a;统计识别方法。 第三类&#xff1a;BP神经网络方法。 第四类&#xff1a;模板匹配方法。 模板匹配方法是最常用的方法。 主要内容 模板匹配的车牌识别包括以下几点主…

前端面试题集合一

Canvas是什么&#xff1f;怎样写Canvas&#xff1f; Canvas是HTML5的一个元素&#xff0c;它使用JavaScript在网页上绘制图形。Canvas是一个矩形区域。它的每一个像素都可以由HTML5语言来控制。使用Canvas绘制路径、框、圆、字符和添加图像有几种方法。 如果要在我们的HTML文…

Socket closed 异常解决方案:如何解决 JMeter 压测中的问题

问题描述 JMeter 压测时会报 java.net.SocketException: Socket closed java.net.SocketException: Socket closed at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) at java.ne…

将项目同时提交到GitHub和码云Gitee上面,GitHub与Gitee同步

多个远程仓库同时使用 新建GitHub仓库 创建成功 在终端中创建仓库 如果你想在本地机器上创建Git仓库&#xff0c;或者想添加一个文件夹或文件到已经存在的Git仓库中&#xff0c;你应该在终端中创建你的Git仓库。在你可以通过终端来创建一个Git仓库。以下是在终端中创建Git仓…

如何选择猫粮:哪种主食冻干猫粮比较好

许多铲屎官可能认为&#xff0c;只需给猫咪喂食猫粮就足够了。然而&#xff0c;猫咪实际上是肉食动物&#xff0c;对蛋白质的需求非常高。冻干猫粮采用低温真空干燥处理技术&#xff0c;将鲜肉经过预冻、升华、解析三个过程&#xff0c;去除水分的同时保持蛋白质等营养物质不变…

虚拟机Linux硬盘扩容

扩容前(20G)&#xff1a; 扩容后(60G)&#xff1a; 步骤&#xff1a; 1. 点击 虚拟机 -> 设置 -> 硬件 -> 硬盘(SCSI) -> 扩展(E)... -> 输入想要扩容大大小 -> 扩展(E) 2. 运行虚拟机&#xff0c;查看根目录属于那个文件系统&#xff0c;我的是 /dev/sda1…

FPGA UDP协议栈:基于88E1111,支持RGMII、GMII、SGMII三种模式,提供3套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的以太网方案本协议栈的 1G-UDP版本本协议栈的 10G-UDP版本本协议栈的 25G-UDP版本1G 千兆网 TCP-->服务器 方案1G 千兆网 TCP-->客户端 方案10G 万兆网 TCP-->服务器客户端 方案 3、该UDP协议栈性能4、详细设计方案设…

CentOS 7 安装私有平台OpenNebula

目录 一、配置yum源 二、配置数据库MySQL 2.1 安装MySQL 2.2 修改MySQL密码 2.3 创建项目用户和库 三、安装配置前端包 四、设置oneadmin账号密码 五、验证安装 5.1 命令行验证安装 5.2 数据存放位置 5.3 端口介绍 5.4 命令介绍 六、访问 6.1 设置语言 6.2 创建主…

ARCGIS PRO SDK 使用条件管理 Pro UI

ARCGIS PRO UI简单介绍以下&#xff1a; 第一步&#xff1a;在Config.daml中在</AddInfo>标签下加上条件<conditions>标签&#xff08;必须添加的&#xff09; <conditions><!-- 定义条件 &#xff0c;此处定义了两个--Tab 另一个为 group><insert…

网络安全复习--简答整理

-----------------------------------------------------教材如上图------------------------------------------------------------ 1.对称加密和非对称加密各有什么特点&#xff1f;加密解密过程中有什么区别&#xff1f;优点P38【考】 对称加密的特点&#xff1a;在针对同一…

oracle 19c容器数据库数据加载和传输-----SQL*Loader(一)

目录 数据加载 &#xff08;一&#xff09;控制文件加载 1.创建用户执行sqlldr 2.创建文本文件和控制文件 3.查看表数据 4.查看log文件 &#xff08;二&#xff09;快捷方式加载 1.system用户执行 2.查看表数据 3.查看log文件 外部表 数据加载和传输的工具&#xff1…

Hyperledger Fabric 生成组织身份解析

fabric 版本 2.4.1 Fabric 网络通过证书和密钥来管理和认证成员身份&#xff0c;经常需要生成证书文件。通常这些操作可以使用 PKI 服务&#xff08;如 Fabric-CA&#xff09;或者 OpenSSL 工具来实现&#xff08;针对单个证书的签发&#xff09;。为了方便批量管理组织证书&am…

Hyperledger Fabric Docker 方式多机部署生产网络

规划网络拓扑 3 个 orderer 节点&#xff1b;组织 org1 , org1 下有两个 peer 节点&#xff0c; peer0 和 peer1; 组织 org2 , org2 下有两个 peer 节点&#xff0c; peer0 和 peer1; 因为我只有 3 台虚拟机资源所以没法实现完全的多机部署&#xff0c;资源使用规划如下&#…

Mac电脑系统提速软件CleanmyMac X2024

Mac是现代人日常工作时必不可少的工具&#xff0c;尤其是在居家办公已经屡见不鲜的当下。视频会议、文档传送、视频剪辑等等。它在工作中扮演的角色越来越重要&#xff0c;所以也导致了它的流畅程度可以在很大程度上影响人们一整天的工作效率和心情。 CleanMyMac X全新版下载如…