本文共 2587 字,大约阅读时间需要 8 分钟。
计算机在执行程序时,每条指令都是在CPU中执行,而在执行指令过程中,就会涉及到数据的读取和写入的问题,由于程序运行过程中的临时数据是存放在主存中的,这时就存在一个问题,由于CPU执行速度很快,而从内存中读取数据和向内存中写入数据的过程跟CPU指令执行的速度比起来要慢得多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度,因此在CPU里面就有了高速缓存
也就是当程序在运行过程中,会将运算所需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存中读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存中。
高速缓存存在一个问题,那就是如果一个共享变量在多个CPU中都存在缓存,那么就有可能出现缓存不一致问题,通常有两种解决方法:这两种方式都是硬件层面上提供的方式
对于第一种方法,是在总线上加LOCK锁的形式来解决缓存不一致问题,因为CPU和其他部件进行通信都是通过总线来进行的,如果对总线加LOCK锁的话,也就是说阻塞了其他CPU对其他不见访问,从而使得只能有一个CPU使用这个变量的内存
第二种方法:缓存一致性协议,比较有代表性的就是MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的,它的核心思想就是:当CPU写数据时,如果发现操作的变量是共享变量,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量是无效的,那么它就会从内存中重新读取
对于可见性(当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立刻看得到修改的值),Java提供了volatile关键字来保证可见性和禁止指令重排
这里解释一下什么是指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序一致。处理器在进行重排序时是会考虑指令之间的数据依赖性的,在单线程中,数据依赖性很强,所以指令重排序不会影响单个线程的执行,但是对于多线程,可能会影响到线程并发执行的正确性
当一个共享变量被volatile修饰时,它会保证修改的值会立即更新到主存中,当有其他线程需要读取时,它会去内存中读取新值,另外通过synchronized和LOCK锁也能够保证可见性,synchronized和LOCK保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码
在Java内存模型中具备一些先天的”有序性“,即不需要通过任何手段就能够得到保证的有序性,这个也通常称为happens-before原则,如果两个操作的执行次序无法从happens-before原则推导出来,那么他们就不能保证他们的有序性,虚拟机可以随意对他们进行重排序
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰后,那么就具备了两层语义:
当我们对一个共享变量加入volatile关键字时会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障,内存屏障会提供三个功能:
Java中可以创建volatile数组,不过只是一个指向数组的引用,意思时,如果该改变引用指向的数组,那么会受到volatile的保护,但是如果多个线程同时改变数组的元素,volatile标识符就不能起到之前的保护作用了
只能在有限的一些情形下使用volatile变量替代锁,要使volatile变量提供理想的线程安全,必须同时满足下面两个条件:
synchronized表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程,volatile表示变量在CPU的寄存器中是不确定的,必须从主存中读取,保证多线程环境下的可见性,禁止指令重排序
区别:转载地址:http://ihjmb.baihongyu.com/