编写线程安全的代码,最核心的是管理对状态的访问,尤其是对共享状态的访问,对象的状态是指存储在状态变量(例如实例或静态域)中的数据
同步机制
当多个线程访问某个状态变量并且只有一个线程执行写入操作时,必须采用同步机制来协同这些线程对变量的访问。java中主要同步机制是关键字synchronized,它通过了一种独占的加锁方式,但“同步”这个术语还包括volatile类型的变量,显式锁(Explicit lock)以及原子变量。
多线程访问状态变量避免出错的三个方式
如果当多个线程访问同一个可变的状态变量时,没有使用适合的同步,那么程序就会出错。有三种方式可以修复这个问题
- 不要在线程间共享状态变量
- 把可变的状态变量改成不可变的
- 在访问变量时使用同步
如果从一开始就设计一个线程安全的类,那么比在以后再将这个类修改为线程安全的类要容易得多。
当设计线程安全的类时,良好的面向对象技术、不可修改性,以及明晰的不变性规范都能起到一定的帮助作用。
线程安全的程序是否完全由线程安全类构成?答案是否定的,完全由线程安全类构成的程序并不一定就是线程安全的,而在线程安全类中也可以包含非线程安全的类
什么是线程安全?
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类就能表现出正确的行为,那么就称这个类是线程安全的。
为什么说ArrayList不是线程安全的
无状态对象:
既不包含任何域,也不包含任何其他类中域的引用的对象。
无状态对象一定是线程安全的。
竞态条件:
当某个计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。
本质是基于一种可能失效的观察结果来做出判断或者执行某个计算。
最常见的竞态条件类型就是“先检查后执行(Check-then-Act)”。
“读取-修改-写入”操作也是一种竞态条件。
例子:单例模式懒汉模式,计数器
原子操作:
对于访问同一个状态的所有操作(包括操作本身)来说,这个操作是一个以原子方式执行的操作。
要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。
包含多个原子操作的复合操作,也不是线程安全的,需要加上同步
内置锁
对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要由同一个锁来保护。
重入
参考
活跃性和性能
通常,在简单性与性能之间存在相互制约的因素。当实现某个同步策略时,一定不要盲目的为了性能而牺牲简单性(可能会破坏安全性)
当执行时间较长的计算或者可能无法快速完成的操作时(例如,网络I/O或控制台I/O)一定不要持有锁
网友评论