所谓线程安全,简单来说就是在多线程环境下,多个线程去访问同一个资源得到的结果都是一致的,正确的;
线程安全问题的具体表现在三个方面,原子性、有序性、可见性。
原子性呢,是指当一个线程执行一系列程序指令操作的时候,它应该是不可中断的,因为一旦出现中断,
站在多线程的视角来看,这一系列的程序指令会出现前后执行结果不一致的问题。
这个和数据库里面的原子性是一样的,就是一段程序只能由一个线程完整地执行完成,而不能存在多个
线程干扰
CPU 的上下文切换,是导致原子性问题的核心,而 JVM 里面提供了 Synchronized 关键字来
解决原子性问题。
可见性呢, 就是说在多线程环境下,由于读和写是发生在不同的线程里面,有可能出现某个线程对共享变
量的修改,对其他线程不是实时可见的。
导致可见性问题的原因有很多,比如 CPU 的高速缓存,L1,L2缓存是CPU私有的,并且CPU会优先从这个 CPU cache中读取数据,在数据没有持久化到内存时,就容易出现数据不一致
比如一个变量 A=10, CPU 1号执行了 A+10,此时CPU 1号中的 A是20,但CPU为了性能考虑,通常是不会使用写直达这种,同时写缓存和内存的方式,而是选择写回这种标记脏页的方式.
若CPU 1号的 A=20没有写入内存的话,
且CPU 2号也去获取变量A,也执行了 A+10,那么CPU 2号的 A也是20.但实际上我们的计算机对 变量A执行了2次 A+10的操作,理应当答案为 A=30;
通常可以使用volatile关键字强行让CPU将数据写入主存中,而不是只写入自己的缓存,
也就是当一个线程修改了 volatile 变量的值后,其他线程能够立即看到最新的值。
因此可以解决可见性问题
有序性,指的是程序编写的指令顺序和最终 CPU 运行的指令顺序可能出现不一致的现象,这种现象也
可以称为指令重排序.
同样可以使用volatile解决有序性问题,因为volatile会禁止处理器对其进行重排序优化
即使编译器和处理器在进行指令重排时也会考虑 volatile 变量的内存屏障,从而确保变量的读写操作按照程序的顺序执行。
因此可以解决有序性问题