2.线程安全问题
2.1线程安全问题
- 非线程安全主要是指多个线程对同一个对象的实例变量进行操作时,会出现值被更改,值不同步的情况
- 线程安全问题表现在三个方面:原子性,可见性和有序性
2.2原子性
原子 Atomic,不可分割的意思,原子操作的不可分割有两层含义
1.访问(读,写)某个共享变量的操作从其他线程来看,该操作要么执行完毕,要么尚未发生,即其他线程看不到当前操作的中间结果
2.访问同一组共享变量的原子操作是不能够交错的,如现实生活中从ATM中取款,对于用户来说,要么操作成功,用户拿到钱,余额减少,增加了交易记录;要么没拿到钱,相当于取款操作没有发生
java有两种实现原子性的方式:一种是使用锁,另一种是利用处理器的CAS (compare and swap)指令
1.锁具有排他性,保证共享变量在某一时刻只能被一个线程访问
2.CAS指令直接在硬件处理器核内存层次上实现,可以看做是硬件锁
2.3可见性
- 在多线程环境中,一个线程对某个共享变量进行更新之后,后续其他的线程可能无法立即读到这个更新的结果,这就是线程安全问题的另外一种形式:可见性visibility
- 如果一个线程对共享变量更新之后,后续访问该变量的其他线程可以读到这个更新的结果,称这个线程对共享变量的更新对其他线程可见,否则称这个线程对共享变量的更新对其他线程不可见
- 多线程程序因为可见性问题可能会导致其他线程读取到了旧数据(脏数据)
2.4有序性
- 有序性Ordering 是指在什么情况下一个处理器运行的一个线程所执行的内存访问操作在另外一个处理器运行的其他线程看来是乱序的Out of Order
- 乱序是指内存访问操作的顺序看起来发生了变化
- 在多核处理器的环境下,我们编写的顺序结构,这种操作执行的顺序可能是没有保障的,
- 编译器可能会改变两个操作的先后顺序
- 处理器可能不会按照目标代码的顺序执行
- 这种一个处理器上执行的多个操作,在其他处理器来看他的顺序与目标代码指定的顺序可能不一样,这种现象叫做重排序
- 重排序是对内存访问有序操作的一种优化,可以在不影响单线程程序正常的情况下提升程序的性能,但是可能对多线程程序的正确性产生影响,导致线程安全问题
- 重排序与可见性问题,不是必然出现的
- 与内存操作顺序有关的几个概念
- 源代码顺序,源码中指定的内存访问顺序
- 程序顺序,处理器上运行的目标代码所指定内存访问顺序
- 执行顺序,内存访问操作在处理器上的实际执行顺序
- 感知顺序,给定处理器所感知到的该处理器及其他处理器的内存访问操作的顺序
- 可以把重排序分为指令重排序和存储子系统重排序两种
- 指令重排序主要是由JIT编译器,处理器引起的,指程序顺序与执行顺序不一样
- 存储子系统重排序是由高速缓存,写缓存器引起的,感知顺序与执行顺序不一样
- 指令重排序
- 在源码顺序与程序顺序不一致,或者程序顺序与执行顺序不一致的情况下,发生了指令重排序Instruction Reorder
- 指令重拍是一种动作,确实对指令的顺序做了调整,重排序的对象是指令
- javac编译器一般不会执行指令重排序,而JIT编译器可能执行指令重排序
- 处理器也能执行指令重排序,使得执行顺序与程序顺序不一致
- 指令重拍不会对单线程程序的结果正确性产生影响, 可能会导致多线程程序出现非预期的结果
- 存储子系统重排序
- 存储子系统是指写缓冲器与高速缓存
- 高速缓存Cache是CPU为了匹配与主存处理速度不匹配而设计的一个高速缓存
- 写缓存器(Store Buffer,Writer Buffer)用来提高写高速缓存操作的效率
- 即使处理器严格按照程序顺序执行两个内存访问操作,在存储子系统的作用下,其他处理器对这两个操作的感知顺序与程序顺序不一致 ,即这两个操作的顺序看起来发生了变化,这种现象称为存储子系统重排序
- 存储子系统重排序并没有真正的对指令执行顺序进行调整,而是造成一种指令执行顺序被调整的现象
- 存储子系统重排序对象是内存操作的结果
- 从处理器角度来看,读内存就是从指定的RAM地址中加载数据到寄存器,称为Load操作,写内存就是数据存储到指定的地址表示的RAM存储单元中,称为Store操作,内存重排序有以下四种可能
- Load Load 重排序,一个处理器先后执行两个读操作L1 L2,其他处理器对这两个内存操作的感知顺序可能是 L2 -> L1
- Store Store 重排序,一个处理器先后执行两个写操作W1 W2,其他处理器对这两个内存操作的感知顺序可能是 W2 -> W1
- Load Store 重排序,一个处理器先执行读操作L1 再执行写操作W1,其他处理器对这两个内存操作的感知顺序可能是 W1-> L1
- Store Load 重排序,一个处理器先执行写操作W1 再执行读操作L1,其他处理器对这两个内存操作的感知顺序可能是L1 -> W1
- 内存重排序与具体的处理器微架构有关,不同架构的处理器所欲怒的内存重排序不同