总结
- Java 内存模型
所有的变量都存储在主内存中。每条线程中海油自己的工作内存,线程的工作内存中保存了被该线程所使用到的变量。线程对变量所有的操作(读取、赋值)都必须在工作内存中进行。不同线程之前无法直接访问,需通过主内存传值。
- 并发编程的三大概念:
原子性、有序性、可见性
原子性
一个或多个操作要么全部执行,要么都不执行,执行的话在执行过程中不会被任何因素打断
Java 中的原子性:只有简单的读取、赋值才是院子操作
x = 10 ==>原子性操作
x = y ==>非院子操作
可见性
当多个线程访问一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值
Java 中的可见性由volatile 关键字来保证,当一个变量被volatile修饰时,他会保证修改的值立即被更新到主存,当有其他线程需要读取时,他会去内存中读取新的值。
此外还有synchronized 和 Lock,他们保证同一时刻只有一个线程获取锁并执行同步代码,并在释放前将对象的变量修改杀心到住存
有序性
即程序的执行顺序按照代码的先后顺序
先来了解下指令重排序:
指令重排序:一般处理器为了提高程序的运行效率,可能会对输入代码进行优化,他不保证程序中各个语句的执行先后顺序同写代码的顺序一致,但是他会保证程序的最终执行结果和代码顺序执行的结果是一致的 -- 处理器在进行重排序时会考虑指令之间的数据依赖性,如果一个指令a 必须用到指令b 的结果,那会保证a 在 b 之前执行 -- 但是:指令重排序只能保证单线程执行的正确性,不能保证多线程执行正确性
Java 中的有序性:
volatile 可以保证"一定的"有序性,synchronized和Lock 可以保证有序性
Java 内存模型具备的有序性:happens-before 原则:
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于写在后面的操作
- 锁定规则:一个unlock 操作先行发生于后面对同一个锁的lock 操作
- volatile变量规则:对一个变量的写操作先行发生于对这个变量的读操作(可用于在单例double check 的时候对变量进行volatile 修饰应用)
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
-线程终结规则:线程中所有的操作都先行发生于线程的终止检测- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
只有同时确保以上三点,才能保证程序的正确执行
深入理解volatile 关键字
- volatile 保证可见性
- volatile 不能保证原子性
- volatile 保证一定的可见性
1)当程序执行到volatile变量的读操作或者写操作时,其前面的操作肯定全部已经进行,并且结果已对后面的操作可见,其后面的操作肯定还没有进行
2)在进行指令优化时,不能将对volatile 变量的读操作或者写操作的语句放在其后执行,也不能把volatile 变量后面的语句放到其前面执行
volatile 实现原理
- 可见性
对声明了volatile变量进行写操作时,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写会到系统内存。为了保证各个处理器缓存一致,每个处理会通过嗅探在总线上传播的数据来检查 自己的缓存是否过期, 当处理器发现自己缓存行对应的内存地址被修改了,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作时,会强制重新从系统内存把数据读到处理器缓存里
- 有序性
Lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成
网友评论