文章插图
何为线程安全的类?一般来说,我们要设计一个线程安全的类,要从三个方面去考虑:
- 构成状态的所有变量 。比如某个域是集合类型,则集合元素也构成该实例的状态 。
- 某些操作所隐含的不变性条件 。
- 变量的所有权,或称它是否会被发布 。
除了不变性条件之外,一些操作还需要通过后验条件,以此判断状态的更改是否有效 。比如一个计数器计到 17 时,它的下一个状态只可能是 18 。这实际涉及到了对原先状态的 "读 - 改 - 写" 三个连续的步骤,典型的如自增 ++ 等 。"无记忆性" 的状态是不需要后验条件的,比如每隔一段时间测量的温度值 。
先验条件可能是更加关注的问题,因为 "先判断后执行" 的逻辑到处存在 。比如说对一个列表执行 remove 操作时,首先需要保证列表是非空的,否则就应该抛出异常 。
在并发环境下,这些条件均可能会随着其它线程的修改而出现失真 。
状态发布与所有权在许多情况下,所有权和封装性是相互关联的 。比如对象通过 private 关键字封装了它的状态,即表明实例独占对该状态的所有权 ( 所有权意味控制权 ) 。反之,则称该状态被发布 。被发布的实例状态可能会被到处修改,因此它们在多线程环境中也存在风险 。
容器类通常和元素表现出 "所有权" 分离的形式 。比如说一个声明为 final 的列表,客户端虽然无法修改其本身的引用,但可以自由地修改其元素的状态 。这些事实上被发布的元素必须被安全地共享 。这要求元素:
- 自身是事实不可变的实例 。
- 线程安全的实例 。
- 被锁保护 。
- 如果这些状态线程不安全,那应该如何安全地使用组合类?
- 即使所有的状态都线程安全,是否可以推断组合类也线程安全?或者说组合类是否还需要额外的同步策略?
class Bank {private Integer amount_A = 100;private Integer amount_B = 50;public synchronized void transaction(Integer amount){var log_0 = amount_A + amount_B;amount_A += amount;amount_B -= amount;var log_1 = amount_A + amount_B;assert log_0 == log_1;}}复制代码
虽然 amount_A 和 amount_B 本身作为普通的 Integer 类型并不是线程安全的,但是它们具备线程安全的语义:privatetransaction()
也可以理解成: Bank 是为两个 Integer 状态提供线程安全性的容器 。在此处,同步策略由 synchronized 内置锁实现 。编译器会在 synchronized 的代码区前后安插 monitorenter 和 monitorexit 字节码表示进入 / 退出同步代码块 。JAVA 的内置锁也称之监视器锁,或者监视器 。
至于第二个问题,答案是:看情况,具体地说是分析是否存在不变性条件,在这里,它指代在转账过程当中,a 和 b 两个账户的余额之和应当不变 。如果使用原子类型保护 amount_A 和 amount_B 的状态,那么是否就可以撤下 transaction() 方法上的内置锁了?
class UnsafeBank {private final AtomicInteger amount_A = new AtomicInteger(100);private final AtomicInteger amount_B = new AtomicInteger(50);public void transaction(Integer amount){amount_A.set(amount_A.get() - amount);amount_B.set(amount_B.get() + amount);}}复制代码
transaction() 方法现在失去了锁的保护 。这样,某线程 A 在执行交易的过程中,另一个线程 B 也可能会 "趁机" 修改 amount_B 的账目 —— 这个时机发生在线程 A 执行 amount_B.get() 之后,但在 amount_B.set() 之前 。最终,B 线程的修改将被覆盖而丢失,在它看来,尽管两个状态均是原子变量,但不变性条件仍然被破坏了 。由此得到一个结论 —— 就算所有的可变状态都是原子的,我们可能仍需要在封装类的层面进一步考虑同步策略,最简单直接的就是找出封装类内的所有复合操作:
推荐阅读
- 使用 JavaScript 实现无限滚动
- 如何自己开发漏洞扫描工具
- Firefox浏览器|火狐浏览器新增实用工具:将支持图片文字识别
- Java|Java:2022年招聘Java开发人员指南
- Java|HR傲慢对待求职者,还“诅咒”对方找不到工作,大学生也太难了
- 什么资源都能搜?这款搜索引擎工具,一键检索各大网盘资源
- 在Java 8及更高版本中使用Java流
- Java实现第三方短信接口发送短信验证码
- 3个超强资源搜索工具 资源搜索工具
- 连裤袜|“颜值经济”盛行,美妆工具受追捧,我国美妆工具市场需求增势明显