java并发编程实战:第十六章----Java内存模型

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能够对它们任意地重排序)

  • 程序顺序规则。若是程序中操做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以前执行。

四、借助同步

”借助(Piggyback)“现有同步机制的可见性属性,对某个未被锁保护的变量的访问操做进行排序(不但愿给对象加锁,而又想维护它的顺序)

 

Happens-Before排序包括:

  • 将一个元素放入一个线程安全容器的操做将在另外一个线程从该容器中得到这个元素的操做以前执行
  • 在CountDownLatch上的倒数操做将在线程从闭锁上的await方法返回以前执行
  • 释放Semaphore许可的操做将在从该Semaphore上得到一个许可以前执行
  • Future表示的任务的全部操做将在从Future.get中返回以前执行
  • 向Executor提交一个Runnable或Callable的操做将在任务开始执行以前执行
  • 一个线程到达CyclicBarrier或Exchange的操做将在其余到达该栅栏或交换点的线程被释放以前执行。若是CyclicBarrier使用一个栅栏操做,那么到达栅栏的操做将在栅栏操做以前执行,而栅栏操做又会在线程从栅栏中释放以前执行

 

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域可达的值,或者在构成过程完成后可能改变的值,必须采用同步来确保可见性

相关文章
相关标签/搜索