线程安全性
要编写线程安全的代码,核心在于要对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问。
「共享」意味着变量可以由多个线程同时访问,「可变」意味着变量的值在其生命周期可以发生变化。
一个对象可以被多个线程访问,那么要让它是线程安全的,需要采用同步机制。Java的主要同步机制:
- 关键字synchronized
- volatile类型的变量
- 显式锁Explicit Lock
- 原子变量
多个线程使用可变的状态变量可能会出现问题,解决办法有:
- 不在线程间共享该变量
- 将变量改为不可变变量
- 在访问变量时使用同步
什么是线程安全性
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协调,这个类始终都能变现出正确的行为,那么就称这个类是线程安全的。
无状态对象一定是线程安全的。
原子性
竞态条件
并发编程中,由于不恰当的执行时序而出现不正确的结果,称为竞态条件。
最常见的竞态条件为Check-Then-Act操作,即通过一个可能失效的观测结果来决定下一步的动作。
「延迟初始化」是常见的Check-Then-Act操作,判断一个对象是否已经初始化,如果没有,则初始化一个新的实例。
复合操作
包含了一组必须以原子方式执行的操作以确保线程安全性,称为复合操作。
当只有一个状态变量时,可以使用线程安全类来保证线程安全性。
加锁机制
当出现不止一个状态变量时,将变量都改为线程安全类,也未必能保证线程安全性。
要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。
内置锁
Java提供了「同步代码块」(Synchronized Block)的内置锁机制来支持原子性。通过关键字synchronized来修饰。1
2
3synchronized (lock) {
// 代码
}
每个Java对象都可以做一个实现同步的锁,这些锁被称为内置锁。锁在线程进入同步代码块之前获得,退出代码块后释放。
在同一时间只能有一个线程持有这种锁,意味着后面的线程会被阻塞,直到前面的线程释放锁。
synchronized使用简单,却会造成比较低的性能。
重入
内置锁的粒度是「线程」,所以当一个线程试图获得一个已经由它自己持有的锁,是可以的。JVM会记录锁的使用者,并且为其计数,当计数值为0时,锁才会被释放。
- 本文链接:https://keepmoving.ren/programming/thread-safty/
- 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!