安全性定义
要编写线程安全的代码, 核心在于对共享的可变的状态访问操作进行管理:
- 共享: 变量可以由多个线程同时访问
- 可变: 变量的值在其生命周期内可以发生变化
java中的同步术语: synchronized关键字, volatile类型的变量, 显式锁以及原子变量.
因此当多个线程访问同一个可变的状态变量时, 要使程序变得线程安全, 有3种方式:
- 不在线程之间共享状态变量
- 将状态变量变为不可变的变量
- 在访问状态变量时使用同步
线程安全性定义: 当多个线程访问某个类时, 这个类始终都能表现出正确的行为, 那么就称这个类是线程安全的.
原子性
- 当某个计算的正确性取决于多个线程的交替执行时序时, 那么就会发生竞态条件.
- 最常见的竞态条件类型是”先检查后执行”操作. (该操作最常见的一种情况就是延迟初始化)
- 原子操作是指对于访问同一个状态的所有操作来说, 这个操作是一个以原子方式执行的操作
内置锁
Java提供了内置的锁机制来支持原子性: Synchronized Block, 同步代码块
每个java对象都可以用作一个实现同步的锁, 这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock), 线程在进入同步代码块之前会自动获取锁, 在退出同步代码块时, 会自动释放锁, 最多只有一个线程能持有这种锁.
重入的一种实现方式是, 为每个锁关联一个获取计数值和一个所有者线程, 当计数值为0时, 表示没有任何线程持有, 当线程请求一个未被持有的锁时, jvm将记录下锁的持有者, 并将获取计数值置为1, 若同一个线程再次获取这个锁, 计数值将递增, 而当线程退出同步代码块时, 计数值将相应递减, 当计数值为0时, 这个锁将被释放.
用锁来保护状态
- 如果使用同步来协调对某个变量的访问, 那么在访问这个变量的所有位置上都需要使用同步, 而且, 当使用锁来协调对某个变量的访问时, 在访问变量的所有位置上都需要使用同一个锁
- 每个共享的和可变的变量都应该只由一个锁来保护
- 对于每个包含多个变量的不变性条件, 其中涉及的所有变量都需要由同一个锁来保护
- 一种常见的加锁约定是, 将所有的可变状态都封装在对象内部, 并通过对象的内置锁对有所有访问可变状态的代码路径进行同步, 使得在该对象上不会发生并发访问, 许多线程安全类中都使用了这种模式
活跃性和性能
应该尽量将不影响共享状态且执行时间较长的操作从同步代码块中分离出去, 从而在这些操作的执行过程中, 其他线程可以访问共享状态