synchronized在jvm中的实现原理、在字节码层面上的实现同步的原理

synchronized在jvm中的是实现原理

java虚拟机中的synchronized是基于进入和退出monitor对象实现的,同步分为显式同步和隐式同步,同步代码块代表着显式同步,指的是有明确的monitorenter和monitorexit指令。同步方法代表着隐式同步,同步方法是由方法调用指令读取运行时常量池中方法的ACC_SYNCHRONIZED标志来隐式实现的。

什么是对象头

对象在堆内存中存在三块区域,包括对象头,实例变量和对齐填充
实例变量,存放着类的属性的相关信息,包括父类的属性信息,如果是数组对象还包括数组的长度
填充数据,仅仅是为了字节对齐,因为java虚拟机要求对象起始地址必须是8字节的整数倍
对象中的对象头是实现synchronized的基础,synchronized使用的锁对象就是存储在java对象中的对象头中,java虚拟机使用2个字来存储对象头,如果对象是数组,使用三个字存储对象头,多出来的一个字用于存放数组的长度。

对象头中的主要结构是MarkWord 和Class Metadata Address组成
其中:
MarkWord中存储对象的hashCode、分代年龄、GC标志、锁信息
MetadataAddress(元数据地址)
jvm通过MetadataAddress这个指针确定该对象是哪一个类的实例

对象的头信息是与对象自身定义的没有关系的额外存储,MarkWord默认情况下存放着对象的hashcode、分代年龄,锁标记位、锁信息等等,考虑对象头信息占用的内存会影响jvm的空间,Markword被设计称为变化的数据结构,它会根据对象本身的状态复用自己的存储空间,如下看MarkWord可能存在的存储结构为:

在这里插入图片描述
重量级锁就是synchronized锁,锁的标记位为10,其中指针指向monitor对象的起始地址。每一个对象都存在一个monitor与之相关联,monitor对象可以与对象一起创建销毁或者当线程试图获取对象锁的时候自动生成,monitor被线程持有之后就处于锁定的状态java虚拟机中monitor是有ObjectMonitor实现的,其主要的数据结构为:

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //记录个数
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

monitor对象中有内部有两个队列,一个用来保存一个ObjectWaiter对象列表(每个等待这个锁的线程都会被封装成为ObjectWaiter对象),owner指向的是持有持有monitor对象的线程,当多个线程同时访问一段代码的时候,首先会进入_Entry_list集合,当线程获取到对象的Monitor后进入,会进入 _owner区域并把monitor中的owner设置为当前线程,同时monitor中的计数器count加1,如果线程调用wait()方法,将会释放当前持有的monitor,owner设置为null,count减1,同时该线程进入WaitSet集合中等待被唤醒,如果当前线程执行完毕也将会释放monitor锁,owner变为null,count减1
总结上面的内容可以知道为什么所有的对象都可以作为锁,另外synchronized是怎么获取锁的,表示锁的monitor对象是什么,monitor对象存在每个java对象的对象头中(MarkWord结构中存储了指向monitor对象的指针),也就是为什么notify,notifyall,wait方法都存在Object对象中的原因
在这里插入图片描述

synchronized在字节码层面的实现原理

**

synchronized修饰的代码块在字节码层面给上的实现原理

**
如下类中包含了代码块

public class SyncCodeBlock {

   public int i;

   public void syncTask(){
       //同步代码库
       synchronized (this){
           i++;
       }
   }
}
Classfile /Users/zejian/Downloads/Java8_Action/src/main/java/com/zejian/concurrencys/SyncCodeBlock.class
  Last modified 2017-6-2; size 426 bytes
  MD5 checksum c80bc322c87b312de760942820b4fed5
  Compiled from "SyncCodeBlock.java"
public class com.zejian.concurrencys.SyncCodeBlock
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
  //........省略常量池中数据
  //构造函数
  public com.zejian.concurrencys.SyncCodeBlock();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
  //===========主要看看syncTask方法实现================
  public void syncTask();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter  //注意此处,进入同步方法
         4: aload_0
         5: dup
         6: getfield      #2             // Field i:I
         9: iconst_1
        10: iadd
        11: putfield      #2            // Field i:I
        14: aload_1
        15: monitorexit   //注意此处,退出同步方法
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit //注意此处,退出同步方法
        22: aload_2
        23: athrow
        24: return
      Exception table:
      //省略其他字节码.......
}
SourceFile: "SyncCodeBlock.java"

上面已经说过synchronized修饰代码块的方式属于显式同步,查看字节码文件之后会发现,synchronized修饰的同步代码块中,从monitorenter进入同步代码块,从monitorexit退出同步代码块。当执行monitorenter指令的时候,当前线程将会试图获取对象头中指向的monitor对象,当monitor对象结构中的conut为0的时候,线程可以成功的获取到monitor,并将count的值设置为1,表示锁获取成功如果当前线程中已经拥有了monitor,那么可以重新进入这个monitor,此时count再加1。如果monitor已经被其他线程持有了,那么当前线程就会被阻塞,只当正在执行的线程执行完毕,执行完monitorexit指令,这时候,owner变为null,count为0,其他线程将有机会持有monitor对象,编译器将会确保无论每一个执行过monitorentor的方法都有一个monitorexit指令与其相对应,不论这个方法时候正常结束,即使是异常也会执行monitorexit指令。这就是synchtonized修饰的同步代码块中为什么出现两个monitorexit指令的原因

synchronized修饰方法的底层实现的原理

如下代码是使用synchronized修饰的方法:

public class SyncMethod {
\
   public int i;

   public synchronized void syncTask(){
           i++;
   }
}
Classfile /Users/zejian/Downloads/Java8_Action/src/main/java/com/zejian/concurrencys/SyncMethod.class
  Last modified 2017-6-2; size 308 bytes
  MD5 checksum f34075a8c059ea65e4cc2fa610e0cd94
  Compiled from "SyncMethod.java"
public class com.zejian.concurrencys.SyncMethod
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool;

   //省略没必要的字节码
  //==================syncTask方法======================
  public synchronized void syncTask();
    descriptor: ()V
    //方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field i:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field i:I
        10: return
      LineNumberTable:
        line 12: 0
        line 13: 10
}
SourceFile: "SyncMethod.java"

之前有说过同步有隐式同步和显式同步,synchronized修饰的同步代码块是显式同步,而synchronized修饰的方法是隐式同步,是通过方法调用指令读取运行时常量池中的ACC_SYNCHRONIZED标志来区分这个方法是否是同步方法。在方法调用的时候会检查方法的ACC_SYNCHRONIZED访问标志是否被设置了,如果被设置了,执行线程将会现持有monitor当方法调用的时候将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置了,如果被设置了,表明是一个同步方法,这个时候,执行线程将会先持有monitor对象(尝试持有,可能没有持有成功),然后再访问执行方法,最后再方法完成的时候,无论是正常完成还是出现异常都会释放monitor。在方法执行期间其他任何一个线程都不能获得同一个monitor对象。如果在方法执行的时候出现了异常并且方法内部无法处理这个异常的时候,这个同步方法持有的monitor将会在异常抛到方法为的时候自动释放
总结synchronized修饰方法可以看出,jvm通过判断ACC_SYNCHRONIZED访问标志来判别一个方法是否是同步方法,进而获取monitor对象,在早期synchronized是重量锁效率低,因为monitor是依赖底层操作系统来实现的,而操作系统实现线程之间的转换需要从用户太转换到核心态,这个转换要浪费很多时间,后来从jvm层面对synchronized有了很大的优化,为了减少获得锁或者是释放锁的性能消耗,引入了轻量级锁和偏向锁,所以现在的synchronized还可以。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. synchronized关键字在使用层面的理解 synchronized关键字是Java用来实现线程同步的关键字,可以修饰方法和代码块。当线程访问被synchronized修饰的方法或代码块时,需要获取对象的锁,如果该锁已被其他线程获取,则该线程会进入阻塞状态,直到获取到锁为止。synchronized关键字可以保证同一时刻只有一个线程能够访问被锁定的方法或代码块,从而避免了多线程并发访问时的数据竞争和一致性问题。 2. synchronized关键字在字节码的体现 在Java代码编译成字节码后,synchronized关键字会被编译成monitorenter和monitorexit指令来实现。monitorenter指令对应获取锁操作,monitorexit指令对应释放锁操作。 3. synchronized关键字在JVM实现JVM,每个对象都有一个监视器(monitor),用来实现对象锁。当一个线程获取对象锁后,就进入了对象的监视器,其他线程只能等待该线程释放锁后再去竞争锁。 synchronized关键字的实现涉及到对象头的标志位,包括锁标志位和重量级锁标志位等。当一个线程获取锁后,锁标志位被设置为1,其他线程再去获取锁时,会进入自旋等待或者阻塞等待状态,直到锁标志位被设置为0,即锁被释放后才能获取锁。 4. synchronized关键字在硬件方面的实现 在硬件层面,锁的实现需要通过CPU指令和总线锁来实现。当一个线程获取锁时,CPU会向总线发送一个锁请求信号,其他CPU收到该信号后会进入自旋等待状态,直到锁被释放后才能获取锁。总线锁可以保证多个CPU之间的原子操作,从而保证锁的正确性和一致性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值