单线程内以及多线程之间的happens-before原则保证线程安全;
一、volatile保证线程安全(非原子性)
有序性(单线程内有序 / 多线程之间有序) + 可见性(一致性)
-
单线程内:单线程中指令重排不会影响线程处理结果,但是可以使用volatile关键字禁止指令重排(volatile的单线程有序性性质,因为会在方法执行过程中相应程序行处加入内存屏障,会实时刷新缓存空间,即时编译器在寄存器中会按照顺序执行缓存行程序);
-
多线程之间:多线程中指令重排会导致最终结果不一致,可以使用volatile关键字(用到的是volatile的一致性性质以及多线程间有序性性质) + 总线嗅探机制避免;
例子:a=b+c
首先总线嗅探保证同一时刻只有一个线程对一个变量a进行修改(其他线程中包含该变量的缓存行会失效);
其次,使用volatile关键字修饰后,各线程必须按照规定的顺序执行对该变量相关的操作(包括对该变量依赖的变量的操作a=b+c,不能以随意顺序改变a或者bc的值)—多线程有序性
最后该变量使用了volatile关键字以后线程对其的修改必须强制实时去内存中读取,修改完后立即强制刷入内存,所以每个线程得到的都是最新的变量值a,也就是说被修饰的变量经过CPU的运算后始终不会被放入寄存器中。(类似于MySQL中的当前读,不会在旧值上修改)—可见性
二、锁保证线程安全
锁也遵循happens-before原则,在一个线程释放锁时要强制刷新一遍其更新的数据到缓存。
可重入锁:当是同一个锁持有线程再次获取锁时,不需要加锁解锁,所以重入时可以省略强制刷缓存的操作,避免消耗CPU资源。
三、final字段保证线程安全
final修饰的变量保证先进行赋值,然后再将该对象放入到共享的内存空间(避免变量还没有赋值完成对象就被线程引用了)。
四、补充
- 多线程指令执行重排序底层实现
线程1:
a = b + c
从内存读取数据 n……再执行 n = n + 1;
线程2:
d = e + f
后续指令行……
CPU执行流程:线程1先启动,执行完第一行指令后加法模块空闲,之后线程1需要从内存中读取数据n,此时线程2已经加载完e、f数据就会抢占获取加法模块,执行第一行指令。
- volatile:轻量级,不保证原子性,但是需要频繁刷缓存,因为无法将该变量存到寄存器(变量级别)
- 锁:重量级,保证原子性,不需要频繁刷缓存(方法,程序块级别)
参考文章:
https://time.geekbang.org/column/article/13484