Java内存可见性,Java内存模型,synchronized原

2020-07-29  本文已影响0人  任笙_8b8c

JVM内存结构、Java对象模型和Java内存模型分别解释:

由Java虚拟机规范定义。描述的是Java程序执行过程中,由JVM管理的不同数据区域。 各个区域有其特定的功能。


内存.png

JDK1.8之前使用永久代实现方法区,1.8之后改用本地内存中实现的元空间代替,把1.8之前的永久代剩余内容全部转移到元空间中.

         而Java对象在JVM中的存储也是有一定的结构的。于Java对象自 身的存储模型称之为Java对象模型。
     HotSpot虚拟机中(Sun JDK和OpenJDK中所带的虚拟机,也是目前使用范围广的Java虚拟机),设 计了一个OOP-Klass Model。OOP(Ordinary Object Pointer)指的是普通对象指针,而Klass用来描 述对象实例的具体类型。
     每一个Java类,在被JVM加载的时候,JVM会给这个类创建一个 instanceKlass 对象,保存在方法区, 用来在JVM层表示该Java类。当我们在Java代码中,使用new创建一个对象的时候,JVM会创建一个 instanceOopDesc 对象,这个对象中包含了对象头以及实例数据。


内存.png

     Java内存模型就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程 序在各种平台下对内存的访问都能保证效果一致的机制及规范。参考:https://www.hollischuang.com/archives/2550
JMM并不像JVM内存结构一 样是真实存在的。他只是一个抽象的概念。JSR-133: Java Memory Model and Thread Specification中 描述了,JMM是和多线程相关的,他描述了一组规则或规范,这个规范定义了一个线程对共享变量的写 入时对另一个线程是可见的。
简单总结下,Java的多线程之间是通过共享内存进行通信的,而由于采用共享内存进行通信,在通信过 程中会存在一系列如可见性、原子性、顺序性等问题,而JMM就是围绕着多线程通信以及与其相关的一 系列特性而建立的模型。JMM定义了一些语法集,这些语法集映射到Java语言中就是volatile、 synchronized等关键字。

内存.png JMM线程操作内存的基本的规则:
小结:

JVM内存结构,和Java虚拟机的运行时区域有关。 Java对象模型,和Java对象在虚拟机中的表现形式有 关。 Java内存模型,和Java的并发编程有关。

内存可见:

  1. 首先,线程 A 把本地内存 A 中更新过的共享变量刷新到主内存中去。
  2. 然后,线程 B 到主内存中去读取线程 A 之前已更新过的共享变量


    内存.png

    如上图所示,本地内存 A 和 B 有主内存中共享变量 x 的副本。假设初始时,这三个内存中的 x 值都为 0。线程 A 在执行时,把更新后的 x 值(假设值为 1)临时存放在自己的本地内存 A 中。当线程 A 和线 程 B 需要通信时,线程 A 首先会把自己本地内存中修改后的 x 值刷新到主内存中,此时主内存中的 x 值变为了 1。随后,线程 B 到主内存中去读取线程 A 更新后的 x 值,此时线程 B 的本地内存的 x 值也 变为了 1。

内存可见性问题
public class Demo1Jmm {
    public static void main(String[] args) throws InterruptedException {
        JmmDemo demo = new JmmDemo();  
      Thread t = new Thread(demo);     
   t.start();    
    Thread.sleep(100);  
     demo.flag = false;    
    System.out.println("已经修改为false");        System.out.println(demo.flag);    }
    static class JmmDemo implements Runnable {        public boolean flag = true;
        public void run() {         
   System.out.println("子线程执行。。。");          
  while (flag) {      
 }          
  System.out.println("子线程结束。。。");  
      } 
解决内存可见性问题:
JMM关于synchronized的两条规定:

线程解锁前(退出同步代码块时):必须把自己工作内存中共享变量的新值刷新到主内存中
线程加锁时(进入同步代码块时):将清空本地内存中共享变量的值,从而使用共享变量时需要从主内 存中重新读取新的值(加锁与解锁是同一把锁)

  while (flag) {       
     synchronized (this) {    
        }    
    }
  1. 获得互斥锁(同步获取锁)
  2. 清空本地内存
  3. 从主内存拷贝变量的新副本到本地内存
  4. 执行代码
  5. 将更改后的共享变量的值刷新到主内存
  6. 释放互斥锁
synchronized的同步可以解决原子性、可见性和有序性的问题,那是如何实现同步的呢?

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

  1. 普通同步方法,锁是当前实例对象this (当前对象)
  2. 静态同步方法,锁是当前类的class对象 (当前类)
  3. 同步方法块,锁是括号里面的对象
    当一个线程访问同步代码块时,它首先是需要得到锁才能执行同步代码,当退出或者抛出异常时必须要 释放锁。

其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。


jvm.png

锁优化

synchronized是重量级锁,效率不高。但在jdk 1.6中对synchronize的实现进行了各种优化,使得它显 得不是那么重了。jdk1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、 偏向锁、轻量级锁等技术来减少锁操作的开销。
锁主要存在四中状态,依次是:
无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞 争的激烈而逐渐升级。

自旋锁
自旋锁的应用场景:
自适应自旋锁 (智能)
锁消除
锁粗化
偏向锁
轻量级锁
轻量锁与偏向锁的不同:

轻量锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁
轻量锁每次进入/退出同步块都需要CAS更新对象头
争夺轻量级锁失败时,自旋尝试抢占锁
可以看到轻量锁适合在竞争情况下使用,其自旋锁可以保证响应速度快,但自旋操作会占用CPU,所以一些计算时间长的操作不适合使用轻量级锁。
==>可以认为 自旋锁 是轻量锁执行中的一部分

重量级锁

重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的 Mutex Lock(互斥锁)实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本 非常高。


锁.png
上一篇下一篇

猜你喜欢

热点阅读