上一篇文章原子性问题的宏观理解 带领你们了解了锁和资源的模型,有了这篇文章的铺垫,相信理解这一篇文章就很是轻松了html
当咱们要保护单个资源并对其进行修改其实很简单,只需按照下图分三步走java
上图的关键是「R1 的锁保护 R1」的指向关系是否正确面试
若是都是保护单个资源这样简单,程序猿的世界该有多美好,惋惜并非,一般咱们须要保护多个资源编程
若是多个资源没有关系,那就是保护一个资源模型的复制,一样很是简单,且看下图:安全
好比现实中银行取款和修改密码操做。 银行取款操做对应的资源是「余额」, 修改密码操做对应的资源是「密码」,余额和密码两个资源彻底没有关系,因此各自用自家的锁保护自家的资源就行了并发
若是多个资源没有关系,程序猿的世界该有多美好,惋惜并非,咱们保护的资源多数状况都有关联关系app
拿经典的银行转帐案例来讲明,帐户 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 和线程 2 能够同时进入 transfer 临界区,上面你认为对的模型其实就会变成这个样子:
还记得 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
就是不能获得咱们理想结果 200,感受生活无比的艰难,那怎么办呢?
上面的问题就是为资源建立的锁不能保护全部关联的资源,那咱们就想办法解决这个问题,来看下面代码:
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 这个级别(分久必合),假设每次转帐业务都很耗时,那么显然这个锁的性能是比较低的,因此接下来的文章,咱们还会继续优化这个模型,选择合适的锁粒度,同时能保护多个有关联的资源,
咱们的锁粒度虽然大,可是咱们保障了帐户的安全,因此并发编程能够先保证事情作对,遇到瓶颈了,慢慢优化改变相应的模型就行了,固然熟练理解这个模型之后,一步到位的并发编程模型固然是极好的......
若是你对这篇文章理解有些困难,能够按照下面的顺序回忆前序文章相关内容
欢迎持续关注公众号:「日拱一兵」
- 前沿 Java 技术干货分享
- 高效工具汇总 | 回复「工具」
- 面试问题分析与解答
- 技术资料领取 | 回复「资料」
以读侦探小说思惟轻松趣味学习 Java 技术栈相关知识,本着将复杂问题简单化,抽象问题具体化和图形化原则逐步分解技术问题,技术持续更新,请持续关注......