1. 指令重排
在计算机执行指令的顺序在经过程序编译器编译之后会形成指令序列,一般情况下,这个指令序列是会输出确定的结果,以确保每一次的执行都有一个确定的结果,但是,一般情况下,CPU和编译器为了提高程序执行的效率,会按照一定规则允许进行指令的优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序。然而在并发执行的情况下,可能会发生二义性,会按照不同的执行逻辑,最终得到一个不同的结果。
JVM----编译的指令----JIT进行执行解释的时候会将指令进行优化
优化:
1.栈上分配
2.标量替换
3.为了提高效率,顺序进行了改变
2. Happens-Before原则
1.程序次序规则:在一个线程内一段代码的执行结果是有序的,就是还发生指令成排,但是无论怎么排,都是根据现有指令进行的,且执行生成的结果不变。
2.管程锁定规则:无论在单线程还是多线程下,对于同一个锁,在一个线程中释放之后,当另一个线程拿到这个锁,都能看到前一个线程的操作结果(管程是一种通用的同步原语,synchronized就是管程的实现)
3.volatile变量规则:如果一个线程先去写一个volatile变量,当另一个线程去读这个变量,那么这个写操作的结果一定是对读的这个线程可见。
4.线程启动规则:在主线程A执行过程中,启动了线程B,那么线程A在启动子线程B之前的共享变量是对线程B是可见的。
5.线程终止规则:在主线程A执行过程中,子线程B终止,那么线程B在终止之前对于共享变量的修改结果在线程A中可见,也称为线程的join()规则。
6.线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()检测是否发生中断。
7.传递性规则:happens-before原则具有传递性。
3. volatile关键字
处理两个问题:
1.可见性问题
JMM内存模型,高速缓冲区,工作内存,会涉及到总线嗅探机制,原子操作过程,他们会使我们其他cpu工作内存中的共享变量失效。
2.指令顺序问题
指令重排概念
总线嗅探机制:
底层实现主要通过一条汇编指令lock前缀指令,他会锁定这块内存区域的缓存(缓存行锁定)并写回到主内存中。
sleep和wait区别:
1.wait会释放资源,sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。锁处理机制不同-sleep()最主要作用使线程暂停执行一段时间,时间一到自动恢复,不涉及线程通讯,因此,调用sleep()方法并不会释放锁。而当调用wait()方法的时候,线程会释放它所占的锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
2.sleep是让某个线程暂停运行一段时间,其控制范围是由当前线程决定,也就是说,在线程里面决定。
3.sleep()是属于Thread类中的,而wait()方法,则是属于Object类中的。
工具:
hsdis-amd64.dll可以把运行的代码的字节码打印出来
案例: 阿里 变量半初始化过程,导致程序异常系统崩溃
单例模式前面加volatile,否则可能出现空指针
volatile关键字是用来处理线程并发中的可见性问题,在底层的实现过程中,volatile对象,不单单是用来处理可见性问题,它也被用来处理顺序问题。
4. lock指令概念
CPU指令 汇编 提供者一般是CPU厂商
Inter AMD 高通 -------搞架构-------指令手册
Inter架构软件开发者手册中对lock指令的解释会将当前处理器的缓存行的数据立即写回到系统内存,这个写回内存操作会引起其他CPU缓存了该地址的数据无效(MESI),提供内存屏障功能,是lock指令不会进行重排。
指令重排的发生是用来提升运行效率的,但不是万能的,只考虑单线程不会出现问题,多线程不能保证。
lock指令会生成内存屏障。
5. 内存屏障
store:将处理器缓存的数据刷新到内存中
Load:将内存存储的数据拷贝到处理器的缓存中
屏障类型 | 指令示例 | 说明 |
---|---|---|
LoadLoad Barriers | Load1;LoadLoad;Load2 | 该屏障确保Load1数据的装载先于Load2其后所有的装载指令的操作 |
StoreStore Barriers | Store1;StoreStore;Store2 | 该屏障确保Store1立刻刷新数据到内存(使其对其他处理器可见)的操作先于Store2及其后所有存储指令的操作 |
LoadStore Barriers | Load1;LoadStore;Store2 | 确保Load1的数据先于Store2及其后所有的存储指令刷新数据到内存的操作 |
StoreLoad Barriers | Store1;StoreLoad;Load2 | 该屏障确保Store1立刻刷新数据到内存的操作先于Load2及其后所有装载指令的操作,它会使该屏障之前的所有内存访问指令(存储指令和访问指令)完成之后,才执行该屏障之后的内存访问指令 |