这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战
线程安全问题
线程安全表现为三个方面:原子性、可见性、有序性。
原子性
原子性是指不可分割的意思。(在读写某个共享变量时候,操作要么执行完,要么没执行,不会出现第三种状态)
java中实现原子性的方式有两种:
- 锁:具有排它性,某一个时刻的共享变量只能被一个进程所访问
- CAS:比较交换,在硬件层次上实现,看做硬件锁
可见性
可见性是指:在多线程中,一个线程对共享变量进行更新之后,后续其他的线程可能无法立即读取到修改后的值。(可详见缓存一致性协议)
有序性
有序性是指在处理器上运行的程序锁执行的内存访问操作,在另一个处理器运行的其他线程看来是乱序的。
重排序
在多核处理器的环境下编写的顺序结构。这种操作执行的顺序可能是没有保障的。
-
编译器可能会改变两个恶操作的先后顺序;
-
处理器可能不会按照目标代码的顺序执行
这种一个处理器上执行的多个操作,在其他处理器看来它的顺序与目标代码指定的顺序不一致,这种现象成为重排序。
重排序可分为指令重排序与储存子系统重排序
- 指令重排序主要是由JIT编译器、处理器引起的,程序顺序与执行顺序不一致。
- 储存子系统重排序是由高速缓存、写缓冲器引起的,感知顺序与执行顺序不一致。
指令重排序
- 在源码顺序与程序顺序不一致,或者程序与执行顺序不一致的情况下,这种就叫指令重排序
- 指令重排是一种动作,对程序做了调整,对指令进行重排
- javac编译器一般不会执行指令重排序,而JIT编译器可能执行指令重排序。
- 处理器可能也可能执行指令重排序,是执行顺序与程序顺序不一致
指令重排序不会对单线程程序的结果的正确性产生影响,可能导致多线程程序出现非预期的结果。
储存子系统重排序
储存子系统是指缓冲器与高速缓存。
- 存储子系统重排序没有真正的对指令顺序做出调整,而是造成一种指令顺序被调整的现象,存储子系统重排序对象是内存操作的结果。
从处理器角度来看, 读内存就是从指定的 RAM 地址中加载数据到寄存器,称为 Load 操作;
写内存就是把数据存储到指定的地址表示的 RAM 存储单元中,称为 Store 操作.内存重排序有以下四种可能:
-
LoadLoad 重排序,一个处理器先后执行两个读操作 L1 和 L2,其他处理器对两个内存操作的感知顺序可能是 L2->L1
-
StoreStore 重排序,一个处理器先后执行两个写操作 W1 和 W2,其他处理器对两个内存操作的感知顺序可能是 W2->W1
-
LoadStore 重排序,一个处理器先执行读内存操作 L1 再执行写内存操作 W1, 其他处理器对两个内存操作的感知顺序可能是 W1->L1
-
StoreLoad重排序,一个处理器先执行写内存操作W1再执行读内存操作 L1, 其他处理器对两个内存操作的感知顺序可能是 L1->W1
内存重排序与具体的处理器微架构有关,不同的处理器所允许的内存排序不同。