第二章 线程安全性
2.0 简介
线程安全的核心: 对 对象状态(包括实例和静态域)操作的访问顺序进行管理.
Java 的主要同步机制:
- synchronize(独占的加锁方式)
- volatile(值被修改后立即对其他线程可见)
- Lock(显示锁, 提供了比使用
synchronized
方法和语句可获得的更广泛的锁定操作) - 原子变量
如何使用多个线程安全的访问某个变量:
- 在不同线程间共享该变量
- 访问变量时进行同步
2.1 什么是线程安全性
定义: 当多个线程访问某个类时, 这个类始终都能表现出正确的行为.
核心概念---正确性:
某个类的行为与其规范完全一致,良好的规范中会定义各种不变性条件约束对象的状态,以及定义各种后验条件描述对象的操作结果.
无状态对象: 既不包含任何域, 也不包含任何对其他类中域的引用.
无状态对象一定是线程安全的.
2.2 原子性
定义: 一个操作要么完整的被执行,要么完全不被执行.
2.2.1 竞态条件
定义: 由于不恰当的执行顺序而导致出现不正确的结果.
常见类型:
- 先检查后执行. 通过一个可能失效的观测结果来决定下一步的操作. 比如延迟初始化.
2.2.2 复合操作
定义: 任务在执行过程中可以被打断的一序列操作.
比如 先检查后执行,读取-修改-写入
2.2.3 原子操作
定义: 任务在执行过程中不会被打断的一序列操作.
2.3 加锁机制
若要保持状态的一致性,则需在单个原子操作中更新所有相关的状态变量.
2.3.1 内置锁
同步代码块(Synchronized Block),主要由两部分构成.
- 一部分(lock)作为锁的对象引用
- 另一部分作为这个锁保护的代码块
synchronized (lock) { //代码块 }
以synchronize修饰的方法为同步方法,实例方法的锁就是调用方法的对象,静态方法的锁就是方法所在的类对象(Class对象)
每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁. 进入方法前自动获得锁,退出方法后自动释放锁.
内置锁相当于一种互斥体(或者互斥锁),意味着最多只有一个线程能持有这种锁.
2.3.2 重入锁
定义: 线程试图获得一个已经由自己持有的锁.
内置锁是可重入的.
可以解决 死锁问题.
重入 意味着获取锁的操作的粒度是线程级别.
实现方式: 为每个锁关联一个计数值和一个所有者线程. 当计数值为0时表示没有被任何线程持有. 当一个线程请求一个未被持有的锁时,JVM将记录下锁的持有者,并将计数器的值置为1,当同一个线程再次获取这个锁,计\计数值将递增,当退出同步代码块时相应的递减. 计数值为0时,这个锁将被释放.
2.4 用锁来保护状态
锁能使其保护的代码以串行形式访问. 也可以通过锁构造一些协议实现对共享状态的独占访问(比如对某个类的全部方法添加synchronized).
当使用锁来保护对某个变量的访问时,那么在访问变量的所有位置上都要使用同一个锁. 此时这个状态变量有这个锁保护.
每个共享和可变的变量都应该由一个锁来保护.
当获取与对象关联的锁时,只能阻止其他线程获得同一个锁,而不能阻止其他线程访问该对象.
当类的不变性条件涉及多个状态变量时,不变性条件的每个变量必须由同一个锁来保护.因此可以在单个原子操作中访问或更新这些变量.确保不变性条件不被破坏.