共享资源的保护:锁机制
点击蓝色“程序猿DD”关注我
回复“资源”获取独家整理的学习资料!
作者 | tan日拱一兵
写在前面
上一篇文章
当我们要保护单个资源并对其进行修改其实很简单,只需按照下图分三步走
创建受保护资源 R 的锁
加锁进入临界区
解锁走出临界区
上图的关键是「R1 的锁保护 R1」的指向关系是否正确
如果都是保护单个资源这样简单,程序猿的世界该有多美好,可惜并不是,通常我们需要保护多个资源
保护多个资源
保护多个没有关系的资源
如果多个资源没有关系,那就是保护一个资源模型的复制,同样非常简单,且看下图:
比如现实中银行取款和修改密码操作。
银行取款操作对应的资源是「余额」, 修改密码操作对应的资源是「密码」,余额和密码两个资源完全没有关系,所以各自用自家的锁保护自家的资源就好了
如果多个资源没有关系,程序猿的世界该有多美好,可惜并不是,我们保护的资源多数情况都有关联关系
保护多个关系的资源
拿经典的银行转账案例来说明,账户 A 给账户 B 转账,账户 A 余额减少 100 元,账户 B 余额增加 100 元,这个操作要是原子性的,那么资源「A 余额」和资源「B 余额」就这样"有了关系",先来看程序:
class Account {private int balance;// 转账synchronized void transfer(Account target, int amt){if (this.balance > amt) {this.balance -= amt;target.balance += amt;}}
}
用 synchronized 直接保护 transfer 方法,然后操作资源「A 余额」和资源「B 余额」就可以了
⚠️: 真的是这样吗?
先停止向下看,在你的笔记本上按照文章开头的三步走来画个图看一看,是否和下图一样呢?
我们通常容易忽略锁和资源的指向关系,我们想当然的用锁 this 来保护 target 资源了,也就没有起到保护作用
假设 A,B,C 账户初始余额都是 200 原,A 向 B 转账 100,B 向 C 转账 100
我们期盼最终的结果是:账户 A 余额: 100 元账户 B 余额: 200 元账户 C 余额: 300 元
假线程 1「A 向 B 转账」与线程 2「B 向 C 转账」两个操作同时执行,根据 JMM 模型可知,线程 1 和线程 2 读取线程 B 当前的余额都是 200 元:
线程 1 执行 transfer 方法锁定的是 A 的实例(A.this),并没有锁定 B 的实例
线程 2 执行 transfer 方法锁定的是 B 的实例(B.this),并没有锁定 C 的实例
还记得 happens-before 规则 这篇文章提到的监视器锁规则和传递性规则吗?
监视器锁规则
对一个锁的解锁 happens-before 于随后对这个锁的加锁
传递性规则
如果 A happens-before B, 且 B happens-before C, 那么 A happens-before C
资源 B.balance 存在于两个"临界区"中,所以这个"临界区"对 B.balance 来说形同虚设,也就不满足监视器锁规则,进而导致传递性规则也不生效,说白了,前序线程的更改结果对后一个线程不可见
这样最终导致:
账户 B 的余额可能是 100: 线程 1 写 B.balance 100(balance = 300) 先于 线程 2 写 B.balance(balance = 100),也就是说线程 1 的结果会被线程 2 覆盖,导致最终账户 B 的余额为 100
账户 B 的余额可能是 300: 与上述情况相反,线程 1 写 B.balance 100(balance = 300) 后于 线程 2 写 B.balance(balance = 100),也就是说线程 2 的结果线程 1 覆盖,导致最终账户 B 的余额为 300
正确姿势
上面的问题就是为资源创建的锁不能保护所有关联的资源,那我们就想办法解决这个问题,来看下面代码:
class Account {private int balance;// 转账void transfer(Account target, int amt){synchronized(Account.class) {if (this.balance > amt) {this.balance -= amt;target.balance += amt;}}}
}
我们将 this 锁变为 Account.class 锁,Account.class 是虚拟机加载 Account 类时创建的,肯定是唯一的(双亲委派模型解释了为何该对象是唯一的), 所有 Account 对象都共享 Account.class, 也就是说,Account.class 锁能保护所有 Account 对象,我们将上面程序再用模型解释一下
总结
到这里关于锁和资源的关系你应该了解的更加透彻了,单个资源和多个无关联资源的情形都很好处理,为各自资源创建相应的锁就好,如果多个资源有关联,为了让锁起到保护作用,我们需要将锁的粒度变大,比如将 this 锁变成了 Account.class 锁。
转账业务非常常见,并发量非常大,如果我们将锁的粒度都提升到 Account.class 这个级别(分久必合),假设每次转账业务都很耗时,那么显然这个锁的性能是比较低的,所以接下来的文章,我们还会继续优化这个模型,选择合适的锁粒度,同时能保护多个有关联的资源,
我们的锁粒度虽然大,但是我们保障了账户的安全,所以并发编程可以先保证事情做对,遇到瓶颈了,慢慢优化改变相应的模型就好了,当然熟练理解这个模型以后,一步到位的并发编程模型当然是极好的......
灵魂追问
还记得 happens-before 的几个原则吗?
偏向锁,轻量锁,重量锁是不是和我们这节内容有异曲同工之处呢?
提前想一下,我们如何来优化这个模型呢?
本文通过OpenWrite的免费Markdown转换工具发布
-END-
留言交流不过瘾
关注我,回复“加群”加入各种主题讨论群
朕已阅
共享资源的保护:锁机制相关推荐
- 程序中任务(中断)间共享资源(临界区)的保护和互斥
一.软件法 1.轮转法 p0 进程:while(turn != 0); //进入区critical section ; //临界区turn = 1; //退出区remainder section; / ...
- ucosIII 共享资源(信号量、互斥信号量)
共享资源: 变量(静态或全局变量).数据结构体.RAM表格.I/O设备等.OS在使用一些资源时候,例如IO设备打印机,当任务1在使用打印机时候必须保证资源独享,避免其他任务修改打印内容导致出错,因此需 ...
- 【Java 并发编程】线程锁机制 ( 锁的四种状态 | 无锁状态 | 偏向锁 | 轻量级锁 | 重量级锁 | 锁竞争 | 锁升级 )
文章目录 一.悲观锁示例 ( ReentrantLock ) 二.重量级锁弊端 三.锁的四种状态 ( 无锁状态 | 偏向锁 | 轻量级锁 | 重量级锁 ) 四.锁的四种状态之间的转换 ( 无锁状态 - ...
- JUC多线程:synchronized锁机制原理 与 Lock锁机制
前言: 线程安全是并发编程中的重要关注点,造成线程安全问题的主要原因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据.因此为了解决这个问题,我们可能需要这样一个方案,当存在多 ...
- Java并发编程(05):悲观锁和乐观锁机制
本文源码:GitHub·点这里 || GitEE·点这里 一.资源和加锁 1.场景描述 多线程并发访问同一个资源问题,假如线程A获取变量之后修改变量值,线程C在此时也获取变量值并且修改,两个线程同时并 ...
- MySQL中的锁机制详细说明
一.MySQL锁机制起步 锁是计算机用以协调多个进程间并发访问同一共享资源的一种机制.MySQL中为了保证数据访问的一致性与有效性等功能,实现了锁机制,MySQL中的锁是在服务器层或者存储引擎层实现的 ...
- openVswitch(OVS)源代码之linux RCU锁机制分析
前言 本来想继续顺着数据包的处理流程分析upcall调用的,但是发现在分析upcall调用时必须先了解linux中内核和用户空间通信接口Netlink机制,所以就一直耽搁了对upcall的分析.如果对 ...
- SQL SERVER的锁机制(二)——概述(锁的兼容性与可以锁定的资源)
二.完整的锁兼容性矩阵(见下图) 对上图的是代码说明:见下图. 三.下表列出了数据库引擎可以锁定的资源. 名称 资源 缩写 编码 呈现锁定时,描述该资源的方式 说明 数据行 RID RID 9 文件编 ...
- 架构设计 | 高并发流量削峰,共享资源加锁机制
本文源码:GitHub·点这里 || GitEE·点这里 一.高并发简介 在互联网的业务架构中,高并发是最难处理的业务之一,常见的使用场景:秒杀,抢购,订票系统:高并发的流程中需要处理的复杂问题非常多 ...
最新文章
- 转载sunboy_2050 - Android APK反编译详解(附图)
- SDN第三次上机作业
- 利用Mac创建一个 IPv6 WIFI 热点
- Java 从入门到高级学习路线
- 优化算法笔记|粒子群算法理解及Python实现
- mysql批量取消多行sql_mysql批量删除指定前缀的表,批量修改表名的SQL语句
- 第一步:python下PyGame的下载与安装
- 在 Chrome 调试 Javascript
- Csrf漏洞概述及其原理
- 2021年上半年数据库系统工程师上午真题及答案解析
- 汇编语言:基本指令详解
- 全球云服务商是怎么排名的?国内云主机市场占有率份额排行对比
- 未来已来,云上安全SaaS化势不可挡
- c语言语法基础知识,英语语法_英语语法基础知识
- 三维点云处理06-2D/3DIoU计算
- 由浅入深分布式(5)dubbo提供者用内网地址注册provider以及 spring boot admin client用主机名注册spring boot admin server
- 基于微信小程序的中国各地美食推荐平台小程序
- 配电室环境监控系统,实现电力设备监控的无人化
- 数学史在数学教育中的重要性
- loadrunner java脚本_用loadrunner11写java脚本小例子(java Vuser)
热门文章
- PostgreSQL(从版本9.3至11.2)任意命令执行漏洞 cve-2019-9193
- linux c setuid函数解析
- 开源流媒体项目 live555 简介
- make mrproper 删除编译产生的相关文件
- VS2017下编译 XP运行程序
- warning C4800: 'int' : forcing value to bool 'true' or 'false' (performance warning)
- 设备驱动程序INF文件——INF文件的节
- Ollydbg 编写脚本的一些语法及例子(OD脚本)
- 不能上网--只要四步判断出故障所在
- java中Error与Exception有什么区别