线程安全问题
线程安全问题
线程安全问题主要从三个层面体现,分别是原子性,可见性,有序性
一、原子性
原子(atomic)就是不可分割的意思,原子操作的不可分割有两层含义
1)访问(读,写)某个共享变量的操作从从其他线程来看,该操作要么已经执行完毕
,要么尚未发生,即其他线程不希望获取到当前线程执行的中间结果
2)访问同一组共享变量的原子操作时不能够交错的
如:现实中ATM机取款,对于用户来说,要么操作成功(用户拿到钱,余额减少),要么操作失败(用户没拿到钱,取款操作没有发生)
java中有两种方式实现了原子性
1)使用锁
2)使用处理器的CAS指令(compare and swap--比较和替换)
锁具有排他性,保证某一共享变量在某一时刻只能被一个线程访问
CAS指令直接在硬件(处理器和内存)层次上实现,可以看作是硬件锁
二、可见性
在多线程环境中,一个线程对某个共享变量进行更新之后,后续其他的线程可能无法
立即读取到这个更新的结果,这就是线程安全问题的另一种形式:可见性(visibility)
如果一个线程对共享变量更新后,后续访问该变量的其他线程可以读到更新的结果
则称这个线程对共享变量的更新对其他线程可见,否则称该线程对共享变量的更新对其他
线程不可见
多线程的可见性问题可能导致其他线程读取到旧数据(脏数据)
三、有序性
有序性(ordering)是指在什么情况下一个处理器上运行的一个线程所执行的内存访问
操作在另外一个处理器运行的其他线程看来是乱序的
乱序是指内存访问操作看起来顺序发生了变化
为了保证线程的有序性,产生了如下几个概念
1)重排序
1.1)指令重排序
在源码顺序与程序顺序不一致,或者程序顺序与执行顺序不一致的情况下 ,我们就说发生了
指令重排序
指令重排是一种动作,确实对指令的顺序做了调整,重排序的对象指令。
javac编译器一般不会执行指令重排序,而jit编译器可能执行指令重排序
处理器也可能执行指令重排序,使得执行顺序和程序顺序不一致
指令重排不会对单线程程序的结果正确性产生影响,可能导致多线程程序出现非预期结果
1.2)存储子系统重排序
存储子系统是指写缓冲器与高速缓存
高速缓存(cache)是cpu中为了匹配主内存的处理速度而设计的一个高速缓存
写缓冲器(store buffer / write buffer)
即使处理器严格按照程序顺序执行两个内存访问操作,在存储子系统的作用下,其他
处理器对这两个操作的感知顺序与程序顺序不一致,即这两个操作的顺序看起来像是
发生了变化,这种现象称为存储子系统重排序,
存储子系统重排序并没有真正的对指令执行顺序进行调整,二是造成一种指令执行顺序
被调整的假象
从处理器角度来看,读内存就是从指定的RAM地址中加载数据到寄存器,称为load操作
,写内存就是把数据存储到指定的地址表示的RAM存储单元中,称为store操作。
内存重排序有以下4种可能
loadload重排序,一个处理器先后执行两个读操作l1和l2,其他处理器对两个内存操作的
感知顺序可能是l2->l1
storesotre重排序,一个处理器先后执行两个写操作w1和w2,其他处理器对两个内存操作
的感知顺序可能是w2->w1
loadstore重排序,一个处理器先执行读内存操作l1再执行写内存w1,
其他处理器对两个内存操作的感知顺序可能是w1->l1
storeload重排序,一个处理器先执行写内存操作w1再执行读内存操作l1,其他处理器对两个
内存操作的感知顺序可能是l1->w1
内存重排序与具体的处理器微架构有关,不同架构的处理器所允许的内存重排序不同,内存重排序可能会导致线程安全问题。
2)貌似串行语义
jit编译器,处理器,存储子系统是按照一定的规则对指令,内存操作的结果进行重排序,给单线程程序造成一种假象--指令是按照源码的顺序执行的,这种假象称为貌似串行语义
四、怎么保证线程安全
可以使用synchronized关键字,synchronized关键字可以保证线程的原子性,可见性,有序性
volatile关键字可以保证共享变量再线程之间可见
五、CAS
CAS(compare and swap)

浙公网安备 33010602011771号