《Java并发编程实战》课程笔记(四)

互斥锁

原子性问题到底该如何解决呢?

  • “同一时刻只有一个线程执行”这个条件非常重要,我们称之为互斥。
  • 如果我们能够保证对共享变量的修改是互斥的,那么,无论是单核 CPU 还是多核 CPU,就都能保证原子性了。

锁模型

在这里插入图片描述

  • 首先,我们要把临界区要保护的资源标注出来,如图中临界区里增加了一个元素:受保护的资源 R;
  • 其次,我们要保护资源 R 就得为它创建一把锁 LR;
  • 最后,针对这把锁 LR,我们还需在进出临界区时添上加锁操作和解锁操作。

Java 语言提供的锁技术:synchronized

  • 锁是一种通用的技术方案,Java 语言提供的 synchronized 关键字,就是锁的一种实现。
  • synchronized 关键字可以用来修饰方法,也可以用来修饰代码块。
    class X {// 修饰非静态方法synchronized void foo() {// 临界区}// 修饰静态方法synchronized static void bar() {// 临界区}// 修饰代码块Object obj = new Object();void baz() {synchronized (obj) {// 临界区}}
    }
    
    • Java 编译器会在 synchronized 修饰的方法或代码块前后自动加上加锁 lock() 和解锁 unlock()。
    • 这样做的好处就是加锁 lock() 和解锁 unlock() 一定是成对出现的,毕竟忘记解锁 unlock() 可是个致命的 Bug(意味着其他线程只能死等下去了)。
    • 当修饰静态方法的时候,锁定的是当前类的 Class 对象,在上面的例子中就是 Class X;
    • 当修饰非静态方法的时候,锁定的是当前实例对象 this

锁和受保护资源的关系

  • 受保护资源和锁之间的关联关系是 N:1 的关系。

保护没有关联关系的多个资源

  • 例如,银行业务中有针对账户余额(余额是一种资源)的取款操作,也有针对账户密码(密码也是一种资源)的更改操作,我们可以为账户余额和账户密码分配不同的锁来解决并发问题,这个还是很简单的。
    class Account {// 锁:保护账户余额private final Object balLock = new Object();// 账户余额private Integer balance;// 锁:保护账户密码private final Object pwLock = new Object();// 账户密码private String password;// 取款void withdraw(Integer amt) {synchronized(balLock) {if (this.balance > amt){this.balance -= amt;}}}// 查看余额Integer getBalance() {synchronized(balLock) {return balance;}}// 更改密码void updatePassword(String pw){synchronized(pwLock) {this.password = pw;}}// 查看密码String getPassword() {synchronized(pwLock) {return password;}}
    }
    
    • 账户类 Account 有两个成员变量,分别是账户余额 balance 和账户密码 password。
    • 取款 withdraw() 和查看余额 getBalance() 操作会访问账户余额 balance,我们创建一个 final 对象 balLock 作为锁(类比球赛门票);
    • 更改密码 updatePassword() 和查看密码 getPassword() 操作会修改账户密码 password,我们创建一个 final 对象 pwLock 作为锁(类比电影票)。
    • 不同的资源用不同的锁保护,各自管各自的,很简单。
    • 用不同的锁对受保护资源进行精细化管理,能够提升性能。这种锁还有个名字,叫细粒度锁。

保护有关联关系的多个资源

  • 例如银行业务里面的转账操作,账户 A 减少 100 元,账户 B 增加 100 元。这两个账户就是有关联关系的。
    class Account {private int balance;// 转账void transfer(Account target, int amt){if (this.balance > amt) {this.balance -= amt;target.balance += amt;}}
    }
    
    • 我们声明了个账户类:Account,该类有一个成员变量余额:balance,还有一个转账的方法:transfer()。
    • 用同一把锁来保护多个资源,也就是现实世界的“包场”,那在编程领域应该怎么“包场”呢?很简单,只要我们的锁能覆盖所有受保护资源就可以了。
  • 用 Account.class 作为共享的锁。
    • Account.class 是所有 Account 对象共享的,而且这个对象是 Java 虚拟机在加载 Account 类的时候创建的,所以我们不用担心它的唯一性。
      class Account {private int balance;// 转账void transfer(Account target, int amt){synchronized(Account.class) {if (this.balance > amt) {this.balance -= amt;target.balance += amt;}}}
      }
      

在这里插入图片描述

  • 相对于用 Account.class 作为互斥锁,锁定的范围太大,而我们锁定两个账户范围就小多了:
    class Account {private int balance;// 转账void transfer(Account target, int amt){// 锁定转出账户synchronized(this) {// 锁定转⼊账户synchronized(target) {if (this.balance > amt) {this.balance -= amt;target.balance += amt;}}}}
    }
    

在这里插入图片描述

  • 使用细粒度锁可以提高并行度,是性能优化的一个重要手段。使用细粒度锁是有代价的,这个代价就是可能会导致死锁。

死锁

  • 死锁的一个比较专业的定义是:一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。

如何预防死锁

  • 并发程序一旦死锁,一般没有特别好的方法,很多时候我们只能重启应用。因此,解决死锁问题最好的办法还是规避死锁。
  • 死锁的四个必要条件:
    • 互斥,共享资源 X 和 Y 只能被一个线程占用;
    • 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
    • 不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
    • 循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。
  • 也就是说只要我们破坏其中一个,就可以成功避免死锁的发生。
    • 对于“占用且等待”这个条件,我们可以一次性申请所有的资源,这样就不存在等待了。
    • 对于“不可抢占”这个条件,占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,这样不可抢占这个条件就破坏掉了。
    • 对于“循环等待”这个条件,可以靠按序申请资源来预防。所谓按序申请,是指资源是有线性顺序的,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样线性化后自然就不存在循环了。

用“等待-通知”机制优化循环等待

  • 一个完整的等待-通知机制:线程首先获取互斥锁,当线程要求的条件不满足时,释放互斥锁,进入等待状态;当要求的条件满足时,通知等待的线程,重新获取互斥锁。

用 synchronized 实现等待-通知机制

  • 在 Java 语言里,等待-通知机制可以有多种实现方式,比如 Java 语言内置的 synchronized 配合 wait()、notify()、notifyAll() 这三个方法就能轻松实现。
    在这里插入图片描述
    • 左边有一个等待队列,同一时刻,只允许一个线程进入synchronized 保护的临界区,当有一个线程进入临界区后,其他线程就只能进入图中左边的等待队列里等待。
    • 这个等待队列和互斥锁是一对一的关系,每个互斥锁都有独立的等待队列。
    • 线程在进入等待队列的同时,会释放持有的互斥锁,线程释放锁后,其他线程就有机会获得锁,并进入临界区了。
  • 那线程要求的条件满足时,该怎么通知这个等待的线程呢?很简单,就是 Java 对象的 notify() 和 notifyAll() 方法。
    • 当条件满足时调用 notify(),会通知等待队列(互斥锁的等待队列)中的线程,告诉它条件曾经满足过。
    • 为什么说是曾经满足过呢?
      • 因为 notify() 只能保证在通知时间点,条件是满足的。而被通知线程的执行时间点和通知的时间点基本上不会重合,所以当线程执行的时候,很可能条件已经不满足了(保不齐有其他线程插队)。
      • 除此之外,还有一个需要注意的点,被通知的线程要想重新执行,仍然需要获取到互斥锁(因为曾经获取的锁在调用 wait() 时已经释放了)。

尽量使用 notifyAll()

  • notify() 是会随机地通知等待队列中的一个线程,而 notifyAll() 会通知等待队列中的所有线程。
  • 实际上使用 notify() 也很有风险,它的风险在于可能导致某些线程永远不会被通知到。所以除非经过深思熟虑,否则尽量使用 notifyAll()。

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

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

相关文章

RK3588平台开发系列讲解(驱动基础篇)设备树常用 of 函数

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、查找节点的 of 函数二、获取属性值的 of 函数三、实验示例3.1、查找的节点代码3.2、获取属性内容代码沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 设备树描述了设备的详细信息,这些信息包括数字类型的…

chatgpt赋能python:Python中-1的用法介绍

Python中-1的用法介绍 什么是-1? 在Python中,-1是一个特殊的索引值,它表示从序列的末尾开始向前数1个元素。这在对于列表、字符串、元组等序列类型进行操作时非常有用。 如何使用-1? 假设我们有一个列表: l [1, …

SpringBoot框架理解

1 SpringBoot入门 1.2 什么是SpringBoot 1 官网的解释 ​ Spring在官方首页是这么说的:说使用SpringBoot可以构造任何东西,SpringBoot是构造所有基于Spring的应用程序的起点,SpringBoot在于通过最少的配置为你启动程序。 2 我的理解 SpringBoot是Sp…

Flask-RESTful的使用

Flask-RESTful的使用 Flask-RESTful基本使用安装定义资源Resources创建API实例添加资源到API运行Flask应用 请求处理请求解析参数校验 响应处理数据序列化定制返回格式 其他功能蓝图装饰器集合路由命名规范路由名称 Flask-RESTful Flask-RESTful是一个用于构建RESTful API的扩展…

计算机对社会的应用是什么,电子计算对人类社会有什么贡献?应用的领域又有哪些?...

在人类历史上,蒸汽机的发明和电力的使用,曾经在生产技术上引起过划时代的工业革命,然而,这种革命,从本质上来讲,它仅仅是涉及到代替人的体力劳动,但电子计算机的发明,已经涉及到代替…

一位美女博士的人脸识别历程

2019-01-28 16:44:37 1月21日,科技评论期刊《麻省理工科技评论》发布了2018年“35岁以下科技创新35人”(35 Innovators Under 35)中国榜单。商汤科技研究总监、年仅29岁的石建萍博士荣登此榜,凭借在计算机视觉原创技术的卓越创新…

Android仿微信发图片的样式,做IM的同学的病有救了

一:前言 最近在搞IM,真的特别痛苦。脑袋大,对于我这种菜鸟来说太难了,比现在社会娶个媳妇还难,硬着头皮搞,终于文字,语音,表情搞完了,开始搞图片,看着微信发…

软件压力测试图片60张,看图测压力,你抗压么?

你压力大么?快跟K线君一起来测测! 平行线 下图里的横线都是平行的 涉世越深的人,受社会侵蚀越严重 看到的直线越变形 你还是单纯的你吗? 你能看出几条笔直的横线? 我想静静 这是一张静止的图片 你的心理压力越大&#…

网络安全入门学习:社会工程学

在电影《我是谁:没有绝对安全的系统》中,主角本杰明充分利用自己高超的黑客技术,非法入侵国际安全系统,并在最后逃之夭夭。在电影中,有一句经典的台词: 所有黑客手段中最有效的、最伟大的幻想艺术——社会…

《计算机组成原理》唐朔飞 第5章 输入输出系统 - 学习笔记

写在前面的话:此系列文章为笔者学习计算机组成原理时的个人笔记,分享出来与大家学习交流。使用教材为唐朔飞第3版,笔记目录大体与教材相同。 网课 计算机组成原理(哈工大刘宏伟)135讲(全)高清_…

easyui 表单提交与图片上传,图片添加、删除

提交表单和图片是web中经常要用到的。我这里用easyui做了一个表单&#xff0c;里面可以上传多张图片&#xff0c;并且可以进行新增和删除。 前端代码如下&#xff1a; <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> …

社会化媒体营销方案简介

最近公司经营走到了死胡同里面&#xff0c;就开始研究运营的营销方案&#xff0c;发现有很多的IT公司都在走社会化营销&#xff0c;感觉这会是以后每个公司都会用到的一种营销策略&#xff01;社会化媒体营销-亦称社会化营销&#xff0c;是利用社会化网络&#xff0c;在线社区&…

神经网络提取图片特征,神经网络算法识别图像

如何用Python和深度神经网络寻找相似图像 代码首先&#xff0c;读入TuriCreate软件包import turicreate as tc我们指定图像所在的文件夹image&#xff0c;让TuriCreate读取所有的图像文件&#xff0c;并且存储到data数据框data tc.image_analysis.load_images(./image/)我们来…

发一张你认为很漂亮的美女照片?

顾锦盒 &#xff0c;INFP, 4w5 565 人赞同 谢谢大家的赞。更新几张。 春风再美也比不过你的笑。 编辑于 2016-04-11 75 条评论 感谢 分享 收藏 • 没有帮助 • 举报 • 作者保留权利 201赞同 反对&#xff0c;不会显示你的姓名 吴名士 &#xff0c;公众号&#xf…

机器学习在社会科学中的应用

本文把目前机器学习技术在社会科学研究中的应用分成三类&#xff1a;第一&#xff0c;数据生成&#xff08;Data Generating Process&#xff09;&#xff1a;机器学习可以帮助学者获得以前很难或无法获得的数据&#xff1b;第二&#xff0c;预测&#xff08;Prediction&#x…

[RUST/腐蚀]Windows-开服服务端下载以及配置

一、前置要求 1.SteamCMD&#xff1a;SteamCMD - Valve Developer Communityhttps://developer.valvesoftware.com/wiki/SteamCMD 2.通过SteamCMD下载RUST/腐蚀服务端。 二、SteamCMD 注意&#xff1a;所有目录均应避免出现中文。 1.建立SteamCMD文件夹&#xff0c;如 D:\st…

chatgpt赋能python:Python中的_--了解这个神秘的下划线

Python中的_ – 了解这个神秘的下划线 Python是一种流行的编程语言&#xff0c;它具有简单易学的语法和强大的功能。一些Python的特殊语法经常会让初学者感到困惑。其中&#xff0c;一个神秘的下划线符号在Python中出现的频率非常高&#xff0c;而且它的含义和使用也非常多样化…

查看MySQL服务器是否启用了SSL连接,并且查看ssl证书是否存在

文章目录 一、查看MySQL服务器是否启用了SSL连接 1.登录MySQL服务器 2.查看SSL配置 二、查看证书是否存在 前言 查看MySQL服务器是否启用了SSL连接&#xff0c;并且查看ssl证书是否存在 一、查看MySQL服务器是否启用了SSL连接 1.登录MySQL服务器 在Linux终端中&#xf…

python向上取整_python向上取整

广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! import math f = 11.2print math.ceil(f) #向上取整print math.floor(f)#向下取整print round(f) #四舍五入 #这三个函数的返回结果都是浮点型... python中向上…

回归预测 | MATLAB实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元多输入单输出回归预测

回归预测 | MATLAB实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元多输入单输出回归预测 目录 回归预测 | MATLAB实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLAB实现SSA-CNN-GRU麻雀算法优…