1、什么是内存模型,为何要使用它缓存
若是缺乏同步,那么将会有许多因素使得线程没法当即甚至永远看到一个线程的操做结果安全
在单线程中,只要程序的最终结果与在严格串行环境中执行的结果相同,那么上述全部操做都是容许的多线程
在多线程中,JVM经过同步操做来找出这些协调操做将在什么时候发生架构
JMM规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操做在什么时候将对其余线程可见app
一、平台的内存模型函数
每一个处理器都拥有本身的缓存,而且按期地与主内存进行协调,在不一样的处理器架构中提供了不一样级别的缓存一致性,即容许不一样的处理器在任意时刻从同一个存储位置上看到不一样的值。JVM经过在适当的位置上插入内存栅栏来屏蔽在JMM与底层平台内存模型之间的差别。Java程序不须要指定内存栅栏的位置,而只需经过正确地使用同步来找出什么时候将访问共享状态性能
二、重排序线程
各类使操做延迟或者看似乱序执行的不一样缘由,均可以归为重排序,内存级的重排序会使程序的行为变得不可预测code
1 Thread one = new Thread(new Runnable() { 2 public void run() { 3 a = 1; 4 x = b; 5 } 6 });
上述代码也会有如下结果:对象
三、Java内存模式简介
Java内存模型是经过各类操做来定义的,包括变量的读/写操做,监视器的加锁和释放操做,以及线程的启动和合并操做
JMM为程序中全部的操做定义了一个偏序关系,称为Happens-Before,使在正确同步的程序中不存在数据竞争(缺少Happens-Before关系,那么JVM能够对它们任意地重排序)
四、借助同步
”借助(Piggyback)“现有同步机制的可见性属性,对某个未被锁保护的变量的访问操做进行排序(不但愿给对象加锁,而又想维护它的顺序)
Happens-Before排序包括:
2、发布
形成不正确发布的真正缘由:"发布一个共享对象"与"另外一个线程访问该对象"之间缺乏一种Happens-Before的关系
一、不安全的发布
除了不可变对象之外,使用被另外一个线程初始化的对象一般都是不安全的,除非对象的发布操做是在使用该对象的线程开始使用以前执行
1 public class UnsafeLazyInitialization { 2 private static Object resource; 3 4 public static Object getInstance(){ 5 if (resource == null){ 6 resource = new Object(); //不安全的发布 7 } 8 return resource; 9 } 10 }
缘由一:线程B看到了线程A发布了一半的对象
缘由二:即便线程A初始化Resource实例以后再将resource设置为指向它,线程B仍可能看到对resource的写入操做将在对Resource各个域的写入操做以前发生。由于线程B看到的线程A中的操做顺序,可能与线程A执行这些操做时的顺序并不相同
二、安全发布
例:BlockingQueue的同步机制保证put在take后执行,A线程放入对象能保证B线程取出时是安全的
借助于类库中如今的同步容器、使用锁保护共享变量、或都使用共享的volatile类型变量,均可以保证对该变量的读取和写入是按照happens-before排序的
happens-before事实上能够比安全发布承诺更强的可见性与排序性
三、安全初始化模式
方式一:加锁保证可见性与排序性,存在性能问题
1 public class UnsafeLazyInitialization { 2 private static Object resource; 3 4 public synchronized static Object getInstance(){ 5 if (resource == null){ 6 resource = new Object(); //不安全的发布 7 } 8 return resource; 9 } 10 }
方式二:提早初始化,可能形成浪费资源
1 public class EagerInitialization { 2 private static Object resource = new Object(); 3 public static Object getInstance(){ 4 return resource; 5 } 6 }
方式三:延迟初始化,建议
1 public class ResourceFactory { 2 private static class ResourceHolder{ 3 public static Object resource = new Object(); 4 } 5 public static Object getInstance(){ 6 return ResourceHolder.resource; 7 } 8 }
方式四:双重加锁机制,注意保证volatile类型,不然出现一致性问题
1 public class DoubleCheckedLocking { 2 private static volatile Object resource; 3 public static Object getInstance(){ 4 if (resource == null){ 5 synchronized (DoubleCheckedLocking.class){ 6 if (resource == null){ 7 resource = new Object(); 8 } 9 } 10 } 11 return resource; 12 } 13 }
3、初始化过程当中的安全性
初始化安全性只能保证经过final域可达的值从构造过程完成时可见性。对于经过非final域可达的值,或者在构成过程完成后可能改变的值,必须采用同步来确保可见性