程序运行过程当中的临时数据存放在主内存(物理内存)当中的。而从内存中读写的速度跟CPU执行指令的速度比起来要慢的多,若是任什么时候候对数据的操做都要经过和内存的交互来进行,会大大下降指令的执行速度。所以CPU里面存在高速缓存。
在程序运行的过程当中,CPU会将运算须要的数据从主存复制一份到其高速缓存中。CPU在进行计算时,直接从其高速缓存读写数据。当运算结束以后,再将高速缓存的数据刷新到主存当中。
当一个变量在多个CPU都存在缓存时,就有可能存在缓存不一致的问题。解决办法:
html
由于CPU和其余部件进行通信时都是经过总线进行的,若是对总线加LOCK#锁,阻塞了其余CPU对其余部件的访问(如主存)。从而使只有一个CPU能使用这个变量的内存。
但在锁住总线期间,其余CPU没法访问内存,致使效率低下。
设计模式
Intel的MESI协议:当CPU写数据时,若是发现操做的数据时共享变量,即在其余CPU中也存在该变量的副本。则会发送信号通知其余CPU将该变量的缓存置为无效状态。所以其余CPU须要读取该变量时,发现自身缓存中该变量的缓存是无效的,则会从主存中从新获取。
缓存
一、原子性:即一个或多个操做,要么所有执行完毕,要么都不执行。
二、可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其余线程可以当即看到修改的值。
三、有序性:程序执行的顺序按照代码的前后顺序执行。(处理器会考虑指令间的数据依赖关系来进行重排序)
如如下例子,代码1和代码2进行重排序对结果并没有影响,但代码3必须在代码1,2以后。便可能出现的排序顺序是 1,2,3 或者 2,1,3
bash
int a = 10;
int b = 17;
a = a + b;
复制代码
Java虚拟机中定义一种Java内存模型以屏蔽各个硬件平台和操做系统的内存访问差别。
Java内存模型没有限制执行引擎使用处理器的寄存器或高速缓存来提高执行指令,也没有限制编译器对指令进行重排序。即Java内存模型也会出现缓存一致性问题和指令重排序的问题。
Java内存模型规定全部的变量都是存在主存中(相似物理内存),每一个线程都有本身的工做内存(相似CPU的高速缓存)。线程对变量的全部操做必须在工做内存中进行,而不能直接对主存进行操做。而且每一个线程不能访问其余线程的工做内存。
多线程
x = 10 //语句1
y = x //语句2
x++ //语句3
x = x + 1 //语句4
复制代码
只有语句1是原子性操做,其余三个语句都不是原子性操做。
语句1直接将10赋值给x,也就是说线程执行这个语句时会直接将数值10写入到工做内存中。而其余语句实际上包含2个操做,先去读取x的值,再将x的值写入工做内存。
并发
总结:Java内存模型只保证简单的读取和赋值(变量之间相互赋值不是原子操做)才是原子操做。想要保证大范围操做的原子性,须要经过synchronized和Lock实现,确保任意时刻只有一个线程执行该代码块。
高并发
Java提供volatile关键字保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会当即被更新到主存中,当有其余线程须要读取时,它会去主存读取新值。
而普通的共享变量不能保证可见性,由于普通共享变量被修改后,何时写入主存是不肯定的。当其余线程去读取,此时内存可能仍是原来的旧值。所以没法保证可见性。
经过synchronized和Lock也可以保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁,而后执行同步代码,而且释放锁以前会将变量的修改刷新到主存当中,所以能够确保可见性。
性能
在Java内存模型中,容许编译器对指令进行重排序,可是重排序不会影响到单线程的执行,却影响到多线程并发执行的正确性。
ui
好比new对象时,会进行三件事件:
(1)、给实例分配内存;
(2)、调用构造方法,初始化成员变量。
(3)、将对象指向分配的内存空间。
而在JVM中(2)和(3)的顺序是没法被保证的,只能经过volalite保证其有序性。spa
volatile的原理和实现机制:
“观察加入volalite关键字和没有加入volalite关键字锁生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前准指令实际上至关于一个内存屏障,内存屏障会提供3个功能:
一、确保指令重排序时,不会把其后面的指令排到内存屏障以前的位置,也不会把前面的指令排到内存屏障的后面。
二、强制对缓存的修改操做当即写入内存。
三、若是是写操做,会致使其余CPU中对应的缓存无效。
synchronized关键字是防止多个线程同时执行一段代码,但会影响执行效率。而volatile关键字在某些状况下性能优于synchronized,但不能替代synchronized,由于volatile不能提供原子性。
1)对变量的写操做不依赖于当前值
2)该变量没有包含在具备其余变量的不变式中
配合计算机的内存模型,能够很好的理解Java的内存模型。以及了解到,可见性在高并发时的重要性。
在确保某个变量(好比某个flag值)在单线程进行修改操做时,可使用volatile确保该变量的可见性。相比synchronized效率有所提高。
在单例DCL中,synchronized时确保了变量的原子性、可见性。但并无确保有序性,这时就须要将变量修饰成volatile来确保有序性
public class Singleton{
private volatile static Singleton mInstance;
private Singleton() {}
public static Singleton getInstance(){
if( mInstance == null){// 语句A
synchronized(Singleton.class){
if( mInstance == null){ // 语句B
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
复制代码
由于当指令进行重排序后会出现如下状况:
(1)、给Singleton的实例分配内存;
(3)、将mInstance对象指向分配的内存空间。
(2)、调用Singleton()的构造方法,初始化成员变量。
当多线程并发时,线程A先运行到语句A中,mInstance是null,线程A进行单例对象的初始化。但由于指令重排序,出现了(1)(3)(2)的状况,当线程A还没执行完(2),也就是还没初始化完单例对象时。线程B运行到语句A。此时单例对象已不为null,天然语句A为false,线程B会返回一个还没初始化完毕的mInstance对象。
一、www.cnblogs.com/dolphin0520… 二、《Android源码设计模式》——《单例设计模式》