锁的相关策略

news2024/11/15 12:45:28

乐观锁vs悲观锁

指的不是具体的锁,是一个抽象的概念,描述的是锁的特性,描述的是一类锁

乐观锁

假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,就让返回用户错误的信息,让用户决定如何去做.(后续做的工作更少)

悲观锁

假设数据最坏的情况,每次去拿数据的时候都认为别人会修改,所以在拿数据的时候都会上锁,这样别人想要拿这个数据就会阻塞直到它拿到锁(后续做的工作更多)
Synchronized初始使用乐观锁策略,当发现锁竞争比较频繁的时候,就会自动切换为悲观锁策略

重量级锁vs轻量级锁

重量级锁

加锁的开销是比较大的(花的时间多,占用系统资源多)
一个悲观锁,很可能是重量级锁(不绝对)

轻量级锁

加锁的开销是比较小的(花的时间少,占用系统资源少)
一个乐观锁,很可能是轻量级锁(不绝对)

乐观悲观,是在加锁之前,对锁冲突的概率进行预测,决定工作的多少
重量轻量,是在加锁之后,考量实际的锁的开销

自旋锁(Spin Lock 轻量级)

在用户态下,通过自旋的方式(while循环),实现类似于加锁的效果的
这种锁,会消耗一定的CPU,但是可以做到快速拿到锁

挂起等待锁(重量级)

通过内核态,借助系统提供的锁机制,当出现锁冲突的时候,会牵扯到内核对于线程的调度,使冲突的线程出现挂起(阻塞等待)
这种方式,消耗的CPU资源的更少的,也就无法保证第一时间拿到锁

读写锁vs互斥锁

读写锁

把读操作加锁和写操作加锁分开了
如果两个线程,一个线程读加锁,另一个线程也是读加锁,不会产生锁竞争
如果两个线程,一个线程写加锁,另一个线程也写加锁,会产生锁竞争
如果两个线程,一个线程写加锁,另一个线程也是读加锁,会产生锁竞争
这里跟数据库事务中的隔离级别中的加锁不太一样
写加锁:写的时候,不能读
读加锁:读的时候,不能写
事务中的读加锁,写加锁,要比这里的读写锁粒度更细,情况分的更多
实际开发中,读操作的频率,往往比写操作,高很多
java标准库中,也提供了现成的读写锁
在这里插入图片描述
在这里插入图片描述
这个类表示一个读锁. 这个对象提供了 lock / unlock 方法进行
加锁解锁.
在这里插入图片描述
这个类表示一个写锁. 这个对象也提供了 lock / unlock 方法进
行加锁解锁.

公平锁vs非公平锁

假设三个线程 A, B, C. A 先尝试获取锁, 获取成功. 然后 B 再尝试获取锁, 获取失败, 阻塞等待; 然后C 也尝试获取锁, C 也获取失败, 也阻塞等待.
当线程 A 释放锁的时候, 会发生啥呢?

公平锁

遵守先来后到,阻塞时间越久的越先得到锁,B 比 C 先来的. 当 A 释放锁的之后, B 就能先于 C 获取到锁.

非公平锁

不遵守先来后到,每个线程都有可能获取到锁,B 和 C 都有可能获取到锁
操作系统内部的线程调度就可以视为是随机的. 如果不做任何额外的限制, 锁就是非公平锁. 如果要想实现公平锁, 就需要依赖额外的数据结构, 来记录线程们的先后顺序.
公平锁和非公平锁没有好坏之分, 关键还是看适用场景
synchronized 是非公平锁

可重入锁vs不可重入锁

可重入锁

可重入锁的字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁,不会出现死锁(阻塞),

不可重入锁

同一个线程,不可以重复获取同一把锁,会出现阻塞的情况

    public synchronized void increase(){
        synchronized (this){
            count++;
        }
    }

代码如果这样写,此时是否会存在问题呢
1.调用方法,先针对this进行加锁,假设此时加锁成功了
2.接下来往下执行到代码块中的synchronized ,此时,还是针对this来进行加锁
此时就会产生锁竞争,当前this对象已经处于加锁状态了,此时,线程就会阻塞,一直阻塞到锁被释放,才能有机会拿到锁,在这个代码中,this上的锁,得在increase方法执行结束之后,才能释放,得第二次加锁成功获取到锁,方法才能继续进行,才能执行完,要想要让代码继续往下执行,就需要把第二次加锁获取到,也就是把第一次加锁释放,想要第一把锁释放,又需要保证代码继续执行
此时,代码在这里就僵住了,这种情况也被称为死锁(死锁的第一种情形)
这里的关键在于,两次加锁,都是同一个线程,第二次尝试加锁的时候,该线程已经有了这个锁的权限了,这个时候,不应该加锁失败的,不应该阻塞等待的
如果是一个不可重入锁,这把锁不会保存,是哪个线程对他的加锁,只要他当前处于加锁状态之后,收到了加锁的请求,收到了加锁这样的请求,就会拒绝当前加锁,而不管当前的线程是哪个,就会产生死锁
可重入锁,则是让这个锁保存,是哪个线程加上的锁,后续接受到加锁的请求之后,就会先对比一下,看看加锁的线程是不是当前持有自己这把锁的线程,这时侯就可以灵活判定了.
synchronized 本身是一个可重入锁

        synchronized (this){
            synchronized (this){
                synchronized (this){
                    ....
                }
            }
        }

这里的嵌套加锁,只有第一个是真正加锁了的,其他的只是进行了判定,并没有真正加锁,那么当我们的代码执行到第一个{}结束的时候,出了这个代码块,刚才加的锁释放要释放:不应该释放
如果在最里层的}处将锁给释放了,就意味着最外面的,以及中间的synchronized 后续的代码部分就没有处在锁的保护中了
真正要释放锁的地方是最后一个}处
那么,系统是怎么区分出哪个}是最后一个}呢
让这个锁持有一个计数器就可以了,让锁对象不仅要记录是哪个线程持有的锁,同时再通过一个整形变量来记录当前这个线程加了几次锁,每次遇到一个加锁就计数器加一,每次遇到一个解锁操作,就减一,当计数器的值被减为0的时候,才是真正执行释放的操作,其他的时候不会释放,这种计数器被称为"引用技术"

死锁

1.两个线程,一把锁,但是是不可重入锁,该线程针对这个锁连续加锁两次,就会出现死锁
2.两个线程,两把锁,这两个线程先分别获取到一把锁,然后再同时尝试获取对方的锁

public class demo4 {
    private static Object locker1 = new Object();
    private static Object locker2 = new Object();
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            synchronized (locker1){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2){
                    System.out.println("t1两把锁加锁成功");
                }
            }

        });
        Thread t2 =new Thread(()->{
            synchronized (locker2){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker1){
                    System.out.println("t2两把锁加锁成功");
                }
            }

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

在这里插入图片描述
结果什么都没打印,这也是出现了死锁的原因
3.三个线程N把锁
在这里插入图片描述
哲学家就餐问题:
5个哲学家就是5个线程
5个筷子就是五把锁
每个作家,主要做两件事
1.思考人生,会放下筷子
2.吃面,会拿起左手和右手的筷子,再去吃面条
其他设定:
1.每个哲学家,什么时候思考人生,什么时候吃面条,不确定
2.一旦吃面条,就一定会固执的完成这个操作,如果此时他的筷子被别人使用了,就会阻塞等待,并且等待的过程不会放下手中已经拿着的筷子
基于上述的模型设定,绝大部分情况下,这些哲学家都是可以很好的工作的
但是,如果出现了极端情况,也会出现死锁
同一时刻,五个哲学家都想吃面,并且同时伸出左手去拿左边的筷子,再尝试拿起右边的筷子
是否有办法避免死锁呢,先得明确死锁产生的原因,死锁的必要条件

死锁的四个必要条件(缺一不可,破一可避免死锁)

1.互斥作用:一个线程获取到一把锁之后,别的线程不能获取到这个锁(实际使用的锁,一般都是互斥的,锁的基本特性)
2.不可抢占:锁只能是被持有者主动释放,而不能是被其他线程直接抢走(锁的基本特性)
3.请求和保持:这个一个线程去尝试获取更多把锁,再获取第二把锁的过程中,会保持对第一把锁的获取状态(取决于代码结构)
4.循环等待:
t1尝试获取locker2,需要t2执行完,释放locker2;
t2尝试获取Locker1,需要t1执行完,释放locker1
(取决于代码结构,解决死锁的最关键的要点)
介绍一个解决死锁的方法:
针对锁进行编号,并且规定加锁的顺序,比如,约定好每个线程如果想要获取多把锁,必须先获取小编号的锁,后获取编号大 的锁,只要所有的线程加锁的顺序都严格按照上述顺序,就一定不会出现循环等待

synchronized 实现的锁策略

1.既是悲观锁也是乐观锁(自适应)
2.既是重量级,也是轻量级(自适应)
3.synchronized 重量级部分是基于互斥锁实现的(挂起等待锁),轻量级部分是基于自旋锁实现的
4.非公平锁
5.可重入锁
6.不是读写锁

内部实现策略(内部原理)

代码中写了一个synchronized 之后,这里可能会产生一系列的"自适应的过程",锁升级(锁膨胀)

无锁->偏向锁->轻量级锁->重量级锁

偏向锁,不是真的加锁,而只是做了一个"标记",如果有别的线程来竞争锁了,才会真的加锁,如果没有别的线程来竞争,就自始至终都不会真的加锁了(加锁本身,是需要一定的开销的)

锁消除

编译器,会智能判定,当前这个代码是否需要加锁,如果写了加锁,而实际上不需要加锁,编译器就会把加锁这个操作自动删除掉

锁粗化

关于锁的粒度:
如果要加锁的操作里要执行的代码越多,就认为锁的粒度更大

               for (...){
                    synchronized (this){
                        count++;
                    }
                }
                synchronized (this){
                    for (...){
                        count++;
                    }
                }

这两个代码显然是第二个代码的粒度更大
有的时候,希望锁的粒度小比较好,并发程度更高
有的时候,希望锁的粒度大比较好,因为锁本身也是有很大的开销

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

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

相关文章

3dsMax软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 3dsMax是一款由Autodesk公司开发的著名的三维计算机图形软件,广泛应用于动画、游戏、建筑和产品设计等领域。它以强大的建模、动画、渲染和特效功能而闻名,为用户提供了一个完整的制作流程,从…

Java设计模式-职责链模式

1 概述 在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同…

【JVM 内存结构丨栈】

栈 -- 虚拟机栈 简介定义压栈出栈局部变量表操作数栈方法调用特点作用 本地方法栈(C栈)定义栈帧变化作用对比 主页传送门:📀 传送 简介 栈是用于执行线程的内存区域,它包括局部变量和操作数栈。 Java 虚拟机栈会为每…

Python 支付宝红包二维码制作步骤分享

<table><tr><td bgcolororange>本文所有教程及源码、软件仅为技术研究。不涉及计算机信息系统功能的删除、修改、增加、干扰&#xff0c;更不会影响计算机信息系统的正常运行。不得将代码用于非法用途&#xff0c;如侵立删&#xff01; 支付宝红包二维码制作…

三方接口调用设计方案

在为第三方系统提供接口的时候&#xff0c;肯定要考虑接口数据的安全问题&#xff0c;比如数据是否被篡改&#xff0c;数据是否已经过时&#xff0c;数据是否可以重复提交等问题 在设计三方接口调用的方案时&#xff0c;需要考虑到安全性和可用性。以下是一种设计方案的概述&a…

go语言中的切片

切片底层 切片&#xff08;Slice&#xff09;是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活&#xff0c;支持自动扩容。 切片是一个引用类型&#xff0c;它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合。 切片…

eclipse中怎么将空白字符显示出来、各种空白字符显示出来是什么样子

空白字符如space、Tab、Ideographic sapce&#xff08;表意空格&#xff0c;就是表意文字中使用的全角空格&#xff09;、回车、换行。 编程规范一般都会要求不用Tab。如果代码中哪个地方用了&#xff0c;但肉眼又看不出来&#xff0c;可以将空白字符暂时显示出来看看。 通过…

知虾shopee数据分析工具:shopee运营选品助手

在如今竞争激烈的电商领域&#xff0c;Shopee商家为了在市场中脱颖而出&#xff0c;必须精准地选品和定价&#xff0c;以满足消费者需求并提升销售。而知虾Shopee数据分析工具作为一款强大的商业助手&#xff0c;为商家提供了丰富的数据分析功能&#xff0c;从而帮助他们更好地…

3.Redis 单线程模型

redis 单线程模型 redis 只使用一个线程来处理所有的命令请求&#xff0c;并不是说一个 redis 服务器进程内部真的就只有一个线程&#xff0c;其实也有多个线程&#xff0c;多个线程是再处理网络 IO。 那么在多线程中&#xff0c;针对类似于这样的场景两个线程尝试同时对一个…

Oracle数据库快速入门

前言&#xff1a; 我想现在很多人的入门数据库都是mysql&#xff0c;但是由于工作中会接触到Oracle数据库&#xff0c;如果你有MySQL的基础的话&#xff0c;这篇文章能让你很快掌握Oracle。 目录 1.体系结构 2.创建用户和表空间 2.1.创建表空间 2.2.创建用户 3.数据类型…

Python | assert关键字

Python断言assert是帮助代码流畅的调试工具。断言主要是假设程序员知道或总是希望是真的&#xff0c;因此将它们放在代码中&#xff0c;这样这些失败不会允许代码进一步执行。 简单地说&#xff0c;断言是一个布尔表达式&#xff0c;用来检查语句是True还是False。如果语句为T…

java八股文面试[多线程]——线程的生命周期

笔试题&#xff1a;画出线程的生命周期&#xff0c;各个状态的转换。 5.等待队列(本是Object里的方法&#xff0c;但影响了线程) 调用obj的wait(), notify()方法前&#xff0c;必须获得obj锁&#xff0c;也就是必须写在synchronized(obj) 代码段内。与等待队列相关的步骤和图 …

AI绘画StableDiffusion实操教程:斗破苍穹-小医仙

之前分享过StableDiffusion的入门到精通教程&#xff1a;AI绘画&#xff1a;Stable Diffusion 终极炼丹宝典&#xff1a;从入门到精通 但是还有人就问&#xff1a;安装是安装好了&#xff0c;可是为什么生成的图片和你生成的图片差距那么远呢&#xff1f; 怎么真实感和质感一…

小研究 - Java虚拟机内存管理(四)

Java 语言的面向对象&#xff0c;平台无关&#xff0c;安全&#xff0c;开发效率高等特点&#xff0c;使其在许多领域中得到了越来越广泛的应用。但是由于Java程序由于自身的局限性&#xff0c;使其无法应用于实时领域。由于垃圾收集器运行时将中断Java程序的运行&#xff0c;其…

电机控制::控制技术与控制要求

功能要求性能要求基本FOC基础控制&#xff08;刚性调参&#xff09;稳、准、快系统性能优化先进控制方案&#xff1a; 精准建模惯量辨识&#xff08;机械参数辨识&#xff1a;辨识 or 观测&#xff09;先进控制方案&#xff08;算法环节&#xff09;SMC、ADRC、自适应、扰动观测…

Ubuntu22.04安装中文输入法►由踩坑到上岸版◄

Ubuntu22.04安装中文输入法►由踩坑到上岸版◄ 了解入坑上岸 更新一发&#xff1a;Gedit中文乱码问题的解决 为了方便回忆和记录甚至后面继续重装系统&#xff0c;我还是写一下以便将来用到或参考&#xff5e; 了解 安装Ubuntu22.04&#xff08;截至2023年08月26日11&#xff…

stm32读写片内flash项目总结(多字节读写tongxindu)

1.flash操作驱动程序 a头文件 #ifndef FLASH_H #define FLASH_H #include “stm32f4xx.h” #define BOARD_NUM_ADDR 0x0800C000 #define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址 #define FLASH_WAITETIME 50000 //FLASH等待超时时间 //FLASH 扇区的起始地址…

石油石化行业网络监控运维方案,全局态势感知,实时预警

石油石化行业是一个高科技密集型行业&#xff0c;投资巨大、人员众多&#xff0c;各产业价值链的关联度较高&#xff0c;大型石油石化企业实现了上中下游产业的一体化协同发展。随着工业4.0时代的来临&#xff0c;信息化和工业化融合&#xff0c;物联网、云计算等新技术的普及推…

Typore 亲测有效(懂得都懂哈)

Typore 亲测从安装到使用&#xff0c;可以使用&#xff08;具体是什么懂得都懂哈&#xff09; 网盘下载地址:链接&#xff1a;https://pan.baidu.com/s/1w0UiS1szxnO9Lxz6sbXEKg?pwdqwe1 提取码&#xff1a;qwe1 第一步&#xff1a; 下载压缩包进行解压&#xff0c;解压过…

Dataset类实践

Dataset类实践 蚂蚁蜜蜂分类数据集和下载链接https://download.pytorch.org/tutorial/hymenoptera_data.zip Dataset&#xff1a;提供一种方式去获取数据及其lable Q&#xff1a;如何获取每个数据及其lable 重写构造方法和获取标签方法 Q&#xff1a;告诉我们总共有多少数据 …