剑指Offer(锁)——JMM的内存模型

Java内存模型(Java Memory Model, 简称JMM)本身是一种抽象的概念并不是真实存在的,描述的是一种规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
在这里插入图片描述
简单说明一下:

  1. 线程内的变量都是存储在栈中的,这些变量被称作线程的私有变量,是存在线程的工作内存中;
  2. JMM要求所有变量的信息,需要放进主内存,线程之间是通过主内存区进行通信的;
  3. 线程无法直接操作主内存,只能通过操作线程操作工作内存,然后写进主内存进行线程交流,线程内部变量都是主内存的副本。

JMM的主内存:

  1. JMM主内存是用来存储所有的Java实例对象的;
  2. 包括成员变量、类信息、常量、静态变量等等;
  3. 由于是数据共享的区域,有可能发生线程安全的问题。

线程内部的工作内存:

  1. 存储当前方法的所有本地变量信息,本地变量对其他线程不可见;
  2. 字节码行号指示器,Native方法信息;
  3. 属于线程私有数据区域,不存在线程安全问题。

JMM和Java内存区域划分是不同的概念层次,JMM描述的是一组规则围绕原子性,有序性和可见性展开。但都是存在共享区域和私有区域的。

总结主内存和工作内存的数据存储类型和操作方式:

  1. 方法内的基本数据类型本地变量将直接存储在工作内存的栈帧结构中;
  2. 引用类型的本地变量会将引用存储在工作内存,实例存储在主内存中;
  3. 成员变量、static、类信息存储在主内存中;
  4. 主内存共享的方式是线程各拷贝一份数据到工作内存中,操作完成刷新回到主内存中。

JMM如何解决可加性的问题的???
在这里插入图片描述
一般修改一个变量都是针对缓存去修改的这一个瞬间缓存是不会自动同步到主内存的,会导致另外一个核的线程无法同步这个修改信息,无法满足一致性。

引入多线程之后,存在很高的数据依赖性,不论编译器和处理器如何重排序都必须尊重数据依赖性的要求,否则破坏了数据的正确性,这是JMM存在的问题。

为了提升性能,编译器经常会使用指令重排序,要求如下:

  1. 单线程环境下不能改变程序运行的结果;
  2. 存在数据依赖关系的不允许重排序。

只有通过Happens-Before原则推出来的才能够进行指令的重排序。

Happens-Before原则:

  1. 程序次序规则:一个线程内,按照代码顺序,书写前面的操作先行发生于书写在后面的操作;
  2. 锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作;
  3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
  4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
  5. 线程启动规则:Thread对象的start方法先行发生于此线程的任何一个动作;
  6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
  7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
  8. 对象终结规则:一个对象的初始化完成先行发生于她的finalize()方法的开始。

如果操作不满足上面的规则,这个操作没有顺序执行的保证,JMM会对这个操作进行重排序,反之如果A Happens-Before操作B,那么操作A在内存上的操作对操作B都是可见的。。

volatile:JVM提供的轻量级同步机制

被volatile修饰的共享变量对所有线程总是可见的,就是当一个线程修改了被volatile修饰的变量,另外一个线程就会立刻知道这个变量的修改情况。

volatile如何保证立刻可见的???

写一个volatile变量时候JMM会把该线程对应的工作内存中的共享变量立刻刷新到主内存中。
读取到一个volatile变量时候JMM把该线程对应的工作内存置为无效。

volatile如何禁止重排优化的???

需要先了解一个概念——内存屏障(Memory Barrier)

  1. 保证特定操作的执行顺序;
  2. 保证某些变量的内存可见性。

通过插入内存屏障来禁止在内存屏障前后的指令执行重排序优化,强制性的刷新出CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。

volatile和synchronized作用相同,那么区别又在哪里呢???

  1. volatile本质是在告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中进行读取;synchronized则是锁定当前变量只有当前线程可以访问该变量,其他线程被阻塞直到该线程完成变量操作为止;
  2. volatile仅能使用在变量级别;synchronized则可以使用在变量、方法和类级别;
  3. volatile仅能实现变量的修改可见性,不能保证原子性;synchronized则可以保证变量修改的可见性和原子性;
  4. volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞;
  5. volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值