管城区-建设局门户网站,百度搜索推广方案,学it一年的学费大概是多少,北京软件开发公司推荐一、什么是内存模型#xff0c;为什么要使用它 如果缺少同步#xff0c;那么将会有许多因素使得线程无法立即甚至永远看到一个线程的操作结果 编译器把变量保存在本地寄存器而不是内存中编译器中生成的指令顺序#xff0c;可以与源代码中的顺序不同处理器采用乱序或并行的方…一、什么是内存模型为什么要使用它 如果缺少同步那么将会有许多因素使得线程无法立即甚至永远看到一个线程的操作结果 编译器把变量保存在本地寄存器而不是内存中编译器中生成的指令顺序可以与源代码中的顺序不同处理器采用乱序或并行的方式来执行指令保存在处理器本地缓存中的值对于其他处理器是不可见在单线程中只要程序的最终结果与在严格串行环境中执行的结果相同那么上述所有操作都是允许的 在多线程中JVM通过同步操作来找出这些协调操作将在何时发生 JMM规定了JVM必须遵循一组最小保证这组保证规定了对变量的写入操作在何时将对其他线程可见 1、平台的内存模型 每个处理器都拥有自己的缓存并且定期地与主内存进行协调在不同的处理器架构中提供了不同级别的缓存一致性即允许不同的处理器在任意时刻从同一个存储位置上看到不同的值。JVM通过在适当的位置上插入内存栅栏来屏蔽在JMM与底层平台内存模型之间的差异。Java程序不需要指定内存栅栏的位置而只需通过正确地使用同步来找出何时将访问共享状态 2、重排序 各种使操作延迟或者看似乱序执行的不同原因都可以归为重排序内存级的重排序会使程序的行为变得不可预测 1 Thread one new Thread(new Runnable() {
2 public void run() {
3 a 1;
4 x b;
5 }
6 }); 上述代码也会有以下结果 3、Java内存模式简介 Java内存模型是通过各种操作来定义的包括变量的读/写操作监视器的加锁和释放操作以及线程的启动和合并操作 JMM为程序中所有的操作定义了一个偏序关系称为Happens-Before使在正确同步的程序中不存在数据竞争缺乏Happens-Before关系那么JVM可以对它们任意地重排序 程序顺序规则。如果程序中操作A在操作B之前那么在线程中A操作将在B操作之前执行监视器锁规则。在监视器锁上的解锁操作必须在同一个监视器锁上的加锁操作之前执行。显式锁和内置锁在加锁和解锁等操作上有着相同的内存语义 volatile变量规则。对volatile变量的写入操作必须在对该变量的读操作之前执行。原子变量与volatile变量在读操作和写操作上有着相同的语义 线程启动规则。在线程上对Thread.start的调用必须在该线程中执行任何操作之前执行线程结束规则。线程中的任何操作都必须在其他线程检测到该线程已经结束之前执行或者从Thread.join中成功返回或者在调用Thread.isAlive时返回false中断规则。当一个线程在另一个线程上调用interrupt时必须在被中断线程检测到interrupt调用之前执行通过抛出InterruptException或者调用isInterrupted和interrupted终结器规则。对象的构造函数必须在启动该对象的终结器之前执行完成传递性。如果操作A在操作B之前执行并且操作B在操作C之前执行那么操作A必须在操作C之前执行。4、借助同步 ”借助Piggyback“现有同步机制的可见性属性对某个未被锁保护的变量的访问操作进行排序不希望给对象加锁而又想维护它的顺序 Happens-Before排序包括 将一个元素放入一个线程安全容器的操作将在另一个线程从该容器中获得这个元素的操作之前执行在CountDownLatch上的倒数操作将在线程从闭锁上的await方法返回之前执行释放Semaphore许可的操作将在从该Semaphore上获得一个许可之前执行Future表示的任务的所有操作将在从Future.get中返回之前执行向Executor提交一个Runnable或Callable的操作将在任务开始执行之前执行一个线程到达CyclicBarrier或Exchange的操作将在其他到达该栅栏或交换点的线程被释放之前执行。如果CyclicBarrier使用一个栅栏操作那么到达栅栏的操作将在栅栏操作之前执行而栅栏操作又会在线程从栅栏中释放之前执行 二、发布 造成不正确发布的真正原因发布一个共享对象与另一个线程访问该对象之间缺少一种Happens-Before的关系 1、不安全的发布 除了不可变对象以外使用被另一个线程初始化的对象通常都是不安全的除非对象的发布操作是在使用该对象的线程开始使用之前执行 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执行这些操作时的顺序并不相同 2、安全发布 例BlockingQueue的同步机制保证put在take后执行A线程放入对象能保证B线程取出时是安全的 借助于类库中现在的同步容器、使用锁保护共享变量、或都使用共享的volatile类型变量都可以保证对该变量的读取和写入是按照happens-before排序的 happens-before事实上可以比安全发布承诺更强的可见性与排序性 3、安全初始化模式 方式一加锁保证可见性与排序性存在性能问题 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 } 三、初始化过程中的安全性 如果能确保初始化过程的安全性被正确构造的不可变对象在没有同步的情况下也能安全地在多个线程之间共享如果不能确保初始化的安全性一些本应为不可变对象的值将会发生改变初始化安全性只能保证通过final域可达的值从构造过程完成时可见性。对于通过非final域可达的值或者在构成过程完成后可能改变的值必须采用同步来确保可见性 转载于:https://www.cnblogs.com/HectorHou/p/6054302.html