乐观锁、悲观锁及死锁

devtools/2024/9/24 2:12:26/

乐观锁、悲观锁

1.概念

  • 悲观锁(悲观锁定):具有强烈的独占和排他特性。在整个执行过程中,将处于锁定状态。悲观锁在持有数据的时候总会把资源或者数据锁住,这样其他线程想要请求这个资源的时候就会阻塞,直到等到悲观锁把资源释放为止. (例如:Synchronized和ReetrantLock)

  • 乐观锁(乐观锁定):乐观锁机制采取了更加宽松的加锁机制。乐观锁在读取时不会上锁,但是乐观锁在进行写入操作的时候会判断当前数据是否被修改过。

    (例如:JAVA中的Stamp锁定和原子整型)

2.锁的使用----读写

ReentranLock

保证了只有一个线程可以执行临界区代码

问题:任何时刻,只允许一个线程执行,不是读线程,就是写线程

👏改进一下:允许多个线程同时读,但只有一个线程在写,其他线程就必须等待

ReadWriteLock

  • 只允许一个线程写入(其他线程既不能写入,也不能读取)

  • 没有写入时,多个线程允许同时读(提高性能)

java"> public class ReadWriteLockTest {private final ReadWriteLock lock = new ReentrantReadWriteLock();private final Lock readLock = lock.readLock();private final Lock writeLock = lock.writeLock();​private int[] counts = new int[10];public void inc(int index){writeLock.lock();try {counts[index]+=1;} finally {writeLock.unlock();}}public int[] get(){readLock.lock();try {return Arrays.copyOf(counts,counts.length);} finally {readLock.unlock();}}}

在读取时,多个线程可以同时获取读锁,大大提高了并发读的执行效率。

问题:如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁,即读的过程中不允许写.

👏改进一下:读的过程中也允许获取写锁写入!

StampedLock

读的数据就可能不一致,需要一点额外的代码来判断读的过程中是否有写入。所以,这种读锁是一种 乐观锁

java">public class Test2 {private final StampedLock stampedLock = new StampedLock();private double x;private double y;public void move(double deltaX,double deltaY){long stamp = stampedLock.writeLock();​try {x += deltaX;y += deltaY;} finally {stampedLock.unlock(stamp);}}public double distanceFromOrigin(){//假设下面两行代码不是原子操作//假设x,y =(100,200)​//获取读锁(乐观锁)long stamp = stampedLock.tryOptimisticRead();​double currentX =x;//此处已读取到x=100,如果没有写入,读取是正确的(100,200)​double currentY =y;//此处已读取到y,如果没有写入,读取是正确的(100,200)//如果有写入,读取是错误的(100,400)​//检查乐观锁的版本号值(stamp)是否一致if(!stampedLock.validate(stamp)){//获取读锁(悲观锁)stamp = stampedLock.tryReadLock();//重新获取try {currentY=y;currentX=x;} finally {stampedLock.unlock(stamp);}}//计算return Math.sqrt(currentX*currentX+currentY*currentY);}}

和ReadWriteLock相比,写入的加锁是完全一样的,不同的是读取

步骤:

1.通过Try OptimisTicRead()获取一个乐观读锁,并返回版本号。

2.进行读取,读取完成后,我们通过验证validate()去验证版本号,如果在读取过程中没有写入,版本号不变,验证成功,继续后续操作.如果在读取过程中有写入,版本号会发生变化,验证将失败。

3.当验证失败时,再通过ReadLock()获取悲观锁再次读取。(由于写入的概率不高,程序在绝大部分情况下可以通过乐观读锁获取数据,极少数情况下使用悲观读锁获取数据)。

所以,StampeLock把读锁细分为乐观读和悲观读,能进一步提升并发效率。

但这也是有代价的:

一是代码更加复杂。

二是Stamp锁定是不可重入锁,不能在一个线程中反复获取同一个锁。

死锁

1.概念

多个线程在运行中,都需要获取对方线程所持有的锁(资源),导致处于长期无限等待的状态。

死锁发生后,只能通过强制结束JVM进程来解决死锁。

java">public class Test3 {public static void main(String[] args) {DeadLock deadLock = new DeadLock();Thread t1 = new Thread(() -> {try {deadLock.add();} catch (InterruptedException e) {e.printStackTrace();}});Thread t2 = new Thread(() -> {deadLock.dec();});t1.start();t2.start();​}}class DeadLock{//两把锁private static Object lockA = new Object();private static  Object lockB = new Object();​public void add() throws InterruptedException {synchronized (lockA){//获得lockA的锁Thread.sleep(100);//线程休眠synchronized (lockB){System.out.println("执行add()");}//释放lockB的锁}//释放lockA的锁}public void dec(){synchronized (lockB){//获得lockB的锁synchronized (lockA){//获得lockA的锁System.out.println("执行dec()");}//释放lockA的锁}//释放lockB的锁}}

2.死锁的条件

产生死锁有四个必要条件:

1.资源互斥:对所分配的资源进行排它性控制,锁在同一时刻只能被一个线程使用;

2.不可剥夺:线程已获得的资源在未使用完之前,不能被剥夺,只能等待占有者自行释放锁:

3.请求等待:当线程因请求资源而阻塞时,对已获得的资源保持不放.

4.循环等待:线程之间的相互等待

3.排查及定位死锁

4.如何避免死锁

1.每次只占用不超过1个锁.

2.按照相同的顺序申请锁.

3.使用信号量


http://www.ppmy.cn/devtools/116271.html

相关文章

828华为云征文 | 将Vue项目部署到Flexus云服务器X实例并实现公网访问

一、Flexus云服务器X实例简介 1.1 概述 华为云Flexus X实例是华为云推出的一款创新云服务器产品,它主要面向中小企业和开发者,旨在解决传统云服务中的痛点,提供更加灵活、高效的云服务体验。 华为深刻洞察了中小企业和开发者在云服务应用中遇…

思维商业篇(5)—发展趋势分析

思维商业篇(5)—发展趋势分析 核心理论 巴菲特曾在《滚雪球》一书中提到他的投资之道其实非常简单,可以总结为两句话:找到足够长的雪道,找到足够湿的雪球。 而发展趋势的分析,正好可以借助巴菲特的这个滚雪球理论。 足够长的雪…

vue3 axios ant-design-vue cdn的方式使用

1、vue3 快速上手 | Vue.js <script src"https://unpkg.com/vue3/dist/vue.global.js"></script><div id"app">{{ message }}</div><script>const { createApp, ref } VuecreateApp({setup() {const message ref(Hello …

PCIe进阶之TL:Request Handling Rules

1 Handling of Received TLPs 本节介绍接收到的 TLP 在数据链路层经过完整性验证之后,这些 TLP 在事务处理层时的处理方式。这些规则如下图所示: 接收侧会忽略保留字段。如果 Fmt 字段显示存在至少一个 TLP Prefix : (1)通过检查后续 DWORD 的第一个字节中的 Fmt 字段,…

408算法题leetcode--第七天

283. 移动零 283. 移动零思路&#xff1a;代码中注释阐述时间&#xff1a;O(n)&#xff1b;空间&#xff1a;O(1) class Solution { public:void moveZeroes(vector<int>& nums) {// 简单思路&#xff1a;用一个辅助数组&#xff0c;将非0元素复制到里面// 双指针&…

設置Android設備全局代理

全局代理是指在設備上設置一個代理伺服器&#xff0c;使所有的網路請求都通過這個代理伺服器進行轉發。這樣&#xff0c;無論你使用的是哪款應用&#xff0c;所有的網路流量都會經過代理伺服器&#xff0c;從而實現統一的網路訪問控制和隱私保護。 配置Wi-Fi網路代理 在Andro…

【已解决】键盘输入数字-使用JAVA语言实现键盘输入的数字再通过快速排序算法输出

文章目录 一、前言任务描述相关知识分治策略&#xff1a;编程要求测试说明 二、具体代码实现总结 一、前言 —快速排序 任务描述 在待排序的n个元素中任取一个元素&#xff08;通常取第一个元素&#xff09;作为基准&#xff0c;把该元素放入最终位置后&#xff0c;整个数据序…

spring怎么识别拦截器 异常处理器

Spring框架中识别和调用拦截器&#xff08;Interceptor&#xff09;和异常处理器&#xff08;HandlerExceptionResolver&#xff09;是通过容器内部的组件扫描和自动装配机制来完成的。 拦截器&#xff08;Interceptor&#xff09;: Spring MVC 的拦截器必须实现 HandlerInte…