常见的锁策略,synchronized优化过程和cas过程

news2024/11/20 11:43:15

1. 常见的锁策略

所谓"策略",也可以理解为做法."锁策略"就是用来描述一把锁面对加锁/解锁时的做法.

1.1 乐观锁 vs 悲观锁

要区分一把锁是乐观锁还是悲观锁,就要预测当前这把锁冲突的概率高不高.

如果冲突概率高,后续要做的工作往往会更多,加锁的开销就大,这就是悲观锁.

如果冲突概率不高,后续要做的工作往往就会少,加锁的开销就少,这就是乐观锁.

那么java中的synchronized是属于哪种锁呢? 答:既是乐观锁,又是悲观锁.

这是因为synchronized支持自适应,能够自己统计出当前锁冲突的次数,进行判定当前锁冲突概率高不高.如果冲突概率高,就按照悲观锁的方式执行;如果冲突概率低,就按照乐观锁的方式执行.

1.2 重量级锁 vs 轻量级锁

区分一把锁是重量级锁还是轻量级锁看的是:对这把锁加锁过程中做的事情多不多.

如果做的事情多,就是重量级锁;如果做的事情少,就是轻量级锁.

一般来说,悲观锁往往是重量级锁;乐观锁往往是轻量级锁.也许以后我们再使用时会混着用这两对概念,但是我们要理解这两对概念的出发点时不同的.

1.3 自旋锁 vs 挂起等待锁

自旋锁,是轻量级锁的一种典型的实现方式.

内部的过程,我们这里用伪代码演示一下:

void lock() {
    while(true) {
        if(锁被占用) {
            continue;
        }
        获取到锁
        break;
    }
}

这个过程中,cpu在空转忙等.消耗了更多的cpu资源.但是,一旦锁被释放,就可以第一时间拿到锁,拿到锁的速度更快.

挂起等待锁,是重量级锁一种典型的实现方式.

所谓"挂起等待',就是在发生锁冲突的时候,进入阻塞等待状态.直到这把锁被释放,系统才能唤醒这个线程去获取这把锁.在这个过程中,由于这个线程说明时候被唤醒是不确定的,因此会花费更长的时间去获取到这把锁,但是cpu不会进行空转,节省了cpu资源.

synchronized的轻量级锁部分,通过自旋锁实现;重量级锁部分,通过挂起等待锁实现.

1.4 可重入锁 vs 不可重入锁

所谓"可重入"和"不可重入"就是同一个线程对这把锁加锁两次,看会不会发生死锁.

不会死锁的就是"可重入锁",会死锁的就是"不可重入锁".

synchronized就是一种可重入锁.这一点我们在之前的博客中已经介绍过了,这里就不过多介绍.

1.5 公平锁 vs 非公平锁

对于公平锁,讲究的是"先来后到",哪个线程等待这把锁的时间最长,哪个线程先获取到这把锁.

对于非公平锁,讲究的是"各凭本事",当这把锁被释放后,哪个线程竞争力强,就哪个线程先获取到这把锁.

synchronized是非公平锁,当这把锁被释放时,多个线程获取到这把锁的概率是被均分的.如果要实现公平锁,需要设置一个线程队列,这样才能排出个"先来后到".

1.6 互斥锁 vs 读写锁

synchronized就是普通的互斥锁.操作:加锁/解锁.

读写锁时一种更特殊的锁.操作:加读锁/加写锁/解锁.

java中的读写锁主要可以由三句话概括:

1. 读锁和读锁之间不互斥

2. 读锁和写锁之间互斥

3. 写锁和写锁之间互斥

主要突出的是,读操作可以同时进行,共享数据.有利于降低锁冲突率,提高并发能力.因为,在日常某些场景中,写操作进行得少,而读操作会进行许多次,这是使用读写锁的优势便能够体现出来.

2. 锁优化 

我们刚才提到了synchronized既是悲观锁,也是乐观锁;既是轻量级锁,也是重量级锁;是可重入锁;是非公平锁;是互斥锁.我们这里来了解synchronized的优化过程.

2.1 锁升级

对于synchronized,会自适应地进行"锁升级",锁升级的过程如下:

未加锁状态->偏向锁->轻量级锁->重量级锁

我们来模拟一下这个过程:

原来线程是未加锁状态,执行synchronized语句升级为偏向锁;如果,遭遇了锁冲突,从偏向锁升级为轻量级锁;如果锁冲突进一步加剧,从轻量级锁升级为重量级锁.

这里我们要对偏向锁这个概念,进行一定的介绍:

这里的偏向锁,就是:加锁了吗?答:如加.实际上就是对这把锁进行一个标记,如果在运行过程中,没有别的线程来获取这把锁,就不对这个线程加锁了;如果有别的线程来获取这把锁,再真正对这个线程加锁(轻量级锁).

锁升级,避免了过度消耗系统的资源.

2.2 锁消除

这是一种编译器优化策略.当我们在代码中使用了synchronized,JVM就会进行判断,这个地方到底需不需要加锁.如果这里不需要加锁,编译器就会自动地把这里的加锁操作给优化掉.

这里最典型的情况就是,在只有一个线程的代码中使用synchronized,这里的加锁操作就会被优化掉.

但是,编译器优化前后要保证代码的逻辑是等价的,因此这里的优化起到的作用是十分有限的.

2.3 锁粗化

锁的粒度:加锁的范围内包含的代码多少.

包含的代码多,锁的粒度就粗;包含的代码少,锁的粒度就细.

在有些逻辑中,需要频繁的加锁和解锁,编译器就会把多个细粒度的锁合并成一把粗粒度的锁,这样在保证等价逻辑的前提下,减少了系统资源的消耗.

3. CAS

CAS的全称是compare and swap,意为比较和交换.这是cpu的一条指令,体现了原子性.

我们可以把CAS的过程想象成一个方法,可以结合下面的伪代码分析:

boolean cas(address,reg1,reg2) {
    if(*address == reg1) {
        把address内存地址的值和reg2寄存器的值进行交换.
        return true;
    }
    return false;
}

address表示获取内存地址,reg指的是cpu的寄存器.这里虽然说的是交换,实际上更多的是用来赋值,而且更关心内存中交换后的值,而不是寄存器里交换后的值.

操作系统内核可以通过cpu的这条指令提供CAS的API;JVM又对系统的CAS API进一步封装,在java代码中也就可以使用CAS操作了.典型的就是"原子类".

原子类的使用

在java.util.concurrent.atomic这个包下,就封装好了许多原子类.

如下图:

比如我们之前提过的案例:使用两个线程交替对同一个变量进行自增.我们分析过,如果不加锁去实现这个代码,会引发线程安全问题.现在我们使用原子类来操作一下:

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

效果如下:

这一套操作被称为"无锁编程",虽然在这种情况下运行效率高了,但是在某些场景下,CAS并不适用.

ABA问题

当前有两个线程并发执行修改变量num的操作,num的初始值为A.

线程t1想要把num的值修改成Z,t1就先读取到num的值存储到oldNum中,然后使用CAS判断,如果当前num的值为A就把他修改成Z.

但是在t1读取到num的值到判断是否要修改的过程中.线程t2进行了修改操作,它先把A改成了B,再把B改成了A.这样看上去好像就没有修改.但是 没有修改 != 修改前后一样.

这样等到线程t1进行判断时,发现num的值仍然是A,会进行修改.但是这与t1操作原先的期望(num的值一直为A就修改为Z)不同,就出现了bug.这就是CAS容易引起bug的"ABA问题".

时间线:

t1获取到num的值A->t2修改num的值为B->t2修改num的值为A->t1判断num的值为A,修改为Z

解决方法:

引入一个只能增加的版本号变量,我们对其中的值每进行一次修改,就让版本号加一.在判断时不仅仅判断值是否相等,同时要判断版本号是否一致.我们引入版本号再来分析一次上面的过程.

假设初始的版本号为1,t1获取到num的值为A,版本号为1;t2修改num的值为B,版本号变为2;t2修改num的值为A,版本号变为3;t1进行判断,值为A,匹配成功,版本号为3 != 1,匹配失败,不进行修改.

 

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

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

相关文章

python画神经网络图

代码1(画神经网络连接图&#xff09; from math import cos, sin, atan import matplotlib.pyplot as plt # 注意这里并没有用到这个networkx这个库&#xff0c;完全是根据matploblib这个库来画的。 class Neuron():def __init__(self, x, y,radius,nameNone):self.x xself.y …

Qt学习记录(C++)——Day 3

目录 一、封装自定义控件 1.添加界面类 2.添加控件 3.提升封装的控件 4.实现功能 5.提供接口 6.测试接口 二、鼠标事件 前言&#xff1a; 1.鼠标进入事件 enterEvent 2.鼠标离开事件 leaveEvent 3.鼠标按下事件 mousePressEvent 4.鼠标释放事件 mouseReleaseEv…

表格头部合并,数据和表头中间插入合计和样式合并

在做项目时其中一个页面要求和Excel格式一样的页面 html数据&#xff0c;大概写了几个案例如下 <el-table v-loading"loading" :data"tableList" :span-method"mergeRow">//:span-method"mergeRow" 用于合并第一行<el-table…

传统图机器学习的特征工程-全图

将整张图表示成为一个低维向量&#xff0c;反映全图的特征 key idea&#xff1a;Bag-of-Words&#xff08;BOW&#xff09;把图看作文章&#xff0c;把节点看作单词 Kernel mothods

基于SSM+Jsp+Mysql的网络视频播放器

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

2024【Java】第十五届蓝桥杯JavaB组第一道填空题

白给题 给了前十项 问第202420242024项是多少 20和24的倍数交替 20 24 40 48 60 72 ........&#xff1b; 实际上就是求24*101210121012 直接用BigInteger 秒了 import java.math.BigInteger;public class Main {public static void main(String[] args) {BigInteger a1 …

2009-2021年上市公司僵尸企业识别数据(含原始数据+计算代码+计算结果)

2009-2021年上市公司僵尸企业识别数据&#xff08;含原始数据计算代码计算结果&#xff09; 1、时间&#xff1a;2009-2021年 2、指标&#xff1a; 证券代码 、证券简称、上市日期、year、净利润、政府补助、流动负债合计、负债合计、财务费用明细利息支出、资产总计、长期负…

Unity绘制地图

首先在项目/Assets文件夹下创建一个Tiles文件夹 在层级下点击鼠标右键选择2D对象选择瓦片地图创建Tilemap。 选择地图素材 如果素材需要裁剪&#xff0c;在检查器Sprite模式选择多个&#xff0c;点击Sprite Editor,选择切 &#xff0c;选择类型Grid By Cell Count&#xff0c;…

ES入门十二:相关性评分

对于一个搜索引擎来说&#xff0c;对检索出来的数据进行排序是非常重要的功能。全文本数据的检索通常无法用是否相等来的出结果&#xff0c;而是用相关性来决定最后的返回结果 相关性是值搜索内容和结果的相关性&#xff0c;是用来描述文档和查询语句的匹配程度的。通过计算相…

Springboot+Vue项目-基于Java+MySQL的校园管理系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

bugku-web-login1

<!DOCTYPE html><html><head lang"en"><meta charset"UTF-8"><title>WEB管理系统</title><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content…

【STL详解 —— list的模拟实现】

STL详解 —— list的模拟实现 list接口总览结点类的模拟实现构造函数 迭代器类的模拟实现迭代器类的模板参数说明构造函数运算符的重载--运算符的重载运算符的重载!运算符的重载* 运算符的重载-> 运算符的重载 list的模拟实现默认成员函数构造函数拷贝构造函数赋值运算符重载…

SQL语法 case when语句用法讲解

CASE WHEN解释 &#xff1a; SQL中的CASE WHEN语句是一种条件表达式&#xff0c;它允许你根据不同的情况返回不同的值。CASE WHEN通常用于SELECT语句中&#xff0c;用于创建新的列&#xff0c;该列的值取决于其他列的值。CASE WHEN可以用于任何可以使用表达式的地方。 大致概…

顺序表(增删减改)+通讯录项目(数据结构)

什么是顺序表 顺序表和数组的区别 顺序表本质就是数组 结构体初阶进阶 系统化的学习-CSDN博客 简单解释一下&#xff0c;就像大家去吃饭&#xff0c;然后左边是苍蝇馆子&#xff0c;右边是修饰过的苍蝇馆子&#xff0c;但是那个好看的苍蝇馆子一看&#xff0c;这不行啊&a…

UI自动化测试中公认最佳的设计模式-POM

一、概念 什么是POM&#xff1f; POM是PageObjectModule&#xff08;页面对象模式&#xff09;的缩写&#xff0c;其目的是为了Web UI测试创建对象库。在这种模式下&#xff0c;应用涉及的每一个页面应该定义为一个单独的类。类中应该包含此页面上的页面元素对象和处理这些元…

css 实现排行榜向上滚动

使用动画实现无线向上滚动 复制一层dom&#xff0c;使用动画向上滚动&#xff0c;鼠标hover的时候暂停动画 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthd…

Java调用WebServices接口

当拿到一个WebServices接口时&#xff0c;首先用接口测试工具调用一下接口&#xff0c;看是否可以正常发送请求和获取返回接口&#xff0c;确保接口是没有问题的&#xff0c;可以用SoapUI工具进行测试。 下面以一个免费的天气预报接口为例&#xff0c;记录整个接口的调用过程。…

【echarts】支持根据data中最大的数值来动态计算y轴最大数据以及间隔

以这个图例可以看到Y轴最大显示到250ml&#xff0c;如果超过就会出现有一部分数据看不到了&#xff1a; https://echarts.apache.org/examples/zh/editor.html?cmix-line-bar 那么如何根据这个data数据的最大值&#xff0c;来动态计算y轴最大数据以及间隔呢&#xff1f; 那我…

数组(java)

目录 数组的定义和使用&#xff1a; 数组的初始化&#xff1a; 遍历数组&#xff1a; 数组是引用类型 初始JVM的内存分布 再读引用变量 认识null 数组的应用场景 作为函数的参数 作为函数的返回值 数组练习 数组转字符串 排序 冒泡排序 数组逆序 数组求平均…

伺服驱动器算法入门的一些建议和书籍推荐

希望此篇文章对想从事伺服驱动器的研发工作的一些刚刚入门的同学一些建议。 针对伺服驱动器的研发工作涉及的知识和需要掌握的技能主要分为两部分&#xff0c;第一是原理部分、第二是工程实践部分。原理部分的学习在此主要推荐大家查看一些入门书籍&#xff0c;本文章中也对书籍…