线程安全问题
非线程安全主要是指多个线程对同一个对象的实例变量进行操作时,会出现值被更改,值的不同步情况
表现为三个方面:原子性,可见性,有序性
原子性就是不可分割的意思,原子操作的不可分割有两层含义
访问(读和写),某个共享变量的操作从其他线程来看,该操作要么已经执行完毕,要么尚未发生,即其他线程不知道当前操作的中间结果
访问同一组共享变量的原子操作是不能够交错的,如现实生活从atm机取款
有两种方式解决这种原子性
一种趋势使用锁,另一种利用处理器的cas指令
锁,具有排他性,保证共享变量某一时刻只能被一个线程访问
cas指令直接在硬件处理器和内存层次上实现,看作是硬件锁
java里面提供一个线程安全的atoicinteger类
可见性
在多线程环境中,一个线程对某个共享变量进行更新之后,后续其他的线程可能无法立即读到这个更新的结果,这就是线程安全问题的另一种形式,可见性
如果一个线程对共享变量更新后,后续访问该变量的其他线程可以读到更新的结果,称这个线程对共享变量的更新对其他线程可见,否则称这个线程对共享变量的更新对其他线程不可见
多线程程序因为可见性问题可能会导致其他线程读到旧数据(脏数据)
有序性
有序性是指在什么情况下,一个处理上运行的一个线程所执行,在内存访问操作在另一个处理器运行的其他线程看来是乱序的
在多核处理器的环境下,编写的顺序结构,这种操作执行的顺序可能是没有保障的
编译器可能会改变两个操作的先后顺序
处理器也可能不会按照目标代码的顺序执行
这一种被其他处理器来看它的顺序与目标代码指定的顺序可能不一样,这种现象称为重排序
重排序是对内存访问有序操作的一种优化,可以在不影响单线程程序正确的情况下提升程序的性能,但是可能对多线程的正确性产生影响,即可能导致线程安全问题
重排序与可见性问题类似,不是必然出现的
与内存操作顺序有关的几个概念
源码顺序,就是源码中指定的内存访问顺序
程序顺序,处理器上运行的代码所指定的内存顺序
执行顺序,内存访问操作在处理器上的实际执行顺序
感知顺序,给定处理器所感知到的该处理器及其他处理器的内存访问操作的顺序
可以把指令重排序与存储子系统重排序两种
指令重排序主要是由JIT编辑器,处理器引起的,指程序顺序与执行顺序不一样
存储子系统重排序是由高速缓存,写缓存引起的,感知顺序与执行顺序不一致
指令重排序
在源码顺序与程序顺序不一致,程序顺序与执行顺序不一致的情况下我们就说发生了指令重排序
指令重排序是一种动作,确实对指令的顺序做了调整,重排序的对象指令
javac编辑器一般不会执行指令重排序,而JIT编辑器可能执行指令重排序
处理器也可能执行指令重排序,使得执行顺序与程序顺序不一致
指令重排不会对单线程程序的结果正确性产生影响,可能导致多线程程序出现非预期的结果
存储子系统是指写缓冲器与高速缓存
高速缓存是CPU中为了匹配与主内存处理速度不匹配而涉的一个高速缓存
写缓冲器,用来提高些高速缓存操作的效率
即使处理器严格按照程序顺序执行两个内存访问操作,在存储子系统的作用下,其他处理器对这两个操作的感知顺序与程序顺序不一致,即这两个操作顺序看起来像是发生了变化,这种现象称为存储子系统重排序
存储子系统重排序并没有真正的对指令执行顺序进行调整,而是造成一种指令执行执行顺序被调整的现象