java类锁和对象锁,java对象锁和类锁全面解析

  java类锁和对象锁,java对象锁和类锁全面解析

  00-1010 1.Java物体结构2。标记字结构信息3。无锁、偏置锁、轻量级锁和重量级锁概述

  

目录

Java对象结构包括三部分:对象头、对象体和填充字节,如图所示:

 

  对象头又包括三个字段:

  第一个字段称为标记字,用于存储自己的运行时数据,如GC标志位、哈希代码、锁状态和其他信息。第二个字段叫做类指针(类对象指针),用来存储方法区中类对象的地址。虚拟机通过这个指针确定这个对象是哪个类实例。第三个字段称为数组长度。如果对象是一个Java数组,这个字段必须可以记录数组长度的数据;如果对象不是Java数组,那么这个字段就不存在,所以是可选字段。在32位JVM虚拟机中,标记字和类指针都是32位的;在64位JVM虚拟机中,标记字和类指针都是64位的。我们需要重点关注的是mark word,因为这关系到synchronized的底层原理。

  00-1010 Java内置锁有四种状态,级别从低到高的顺序是:无锁、有偏锁、轻量级锁、重量级锁。其实在JDK 1.6之前,Java内置锁是一个重量级锁,也是一个效率很低的锁。JDK 1.6之后,为了提高获取和释放锁的效率,JVM优化了synchronized的实现,引入了偏向锁和轻量锁。此后Java内置锁出现了四种状态,四种状态会随着竞争逐渐升级,而且是不可逆的过程,即不能降级。

  1、不同锁状态下的Mark Word字段结构:

  结构标记字字段与Java内置锁的状态密切相关。为了使标记字字段存储更多的信息,JVM将标记字的最低两位设置为Java内置锁状态位。不同锁定状态下32位标志字的结构如下表所示:

  64位标记字的结构类似于32位标记字的结构。结构如下表所示:

  2、64位Mark Word的介绍:

  目前主流的JVM都是64位的,所以我们用64位的Mark Word。接下来详细介绍64位标记字中各部分的内容。

  (1) :设置占用两个二进制位的锁定状态标志位,是因为希望尽可能少的二进制位能够表示尽可能多的信息。当标记的值不同时,整个标记词的意义也不一样。

  (2)biased _ lock:对象是否启用了biased lock标志,该标志只占用1个二进制位。值1表示对象启用了偏置锁定,值0表示对象没有偏置锁定。和lock biased_lock组合起来指示对象实例处于哪种锁状态。组合的含义如表2-3所示。

  和lock biased_lock组合起来指示对象实例处于哪种锁状态。组合的含义如下表所示:

  (3) ptr _ to _ lock _ record:62位,轻量锁状态下堆栈帧中锁记录的指针。

  (4) ptr _ to _ heavy _ monitor:62位指针,指向处于重量级锁定状态的对象监视器。

  00-1010在1.6版之前,所有的Java内置锁都是重量级锁。重量级锁会导致CPU在用户模式和核心模式之间频繁切换,因此成本高,效率低。1.6版本引入了偏置锁和轻量级锁的实现,以减少获取和释放锁所造成的性能消耗。因此,在1.6版本中,内置锁有四种状态:无锁状态、偏锁状态、轻量级锁状态和重量级锁状态,随着竞争逐渐升级。内置锁可以升级但不能降级,也就是说有偏锁升级为轻量锁后不能降级为有偏锁。这种可升级不可降级的策略旨在提高获取和释放锁的效率。

  (1) 无锁状态 :

  Java对象刚创建的时候,没有线程竞争,说明对象处于解锁状态(没有线程竞争)。此时,偏置锁定标志位为0,锁定状态为01;

  (2) 偏向锁状态:

  偏向锁意味着一段同步代码一直被锁定。

  一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。如果内置锁处于偏向状态,当有一个线程来竞争锁时,先用偏向锁,表示内置锁偏爱这个线程,这个线程要执行该锁关联的同步代码时,不需要再做任何检查和切换。偏向锁在竞争不激烈的情况下效率非常高。

  偏向锁状态的Mark Word会记录内置锁自己偏爱的线程ID,内置锁会将该线程当作自己的熟人,这时偏向锁标识位是1,锁状态是01;

  (3) 轻量级锁状态:

  当有两个线程开始竞争这个锁对象时,情况就发生变化了,不再是偏向(独占)锁了,锁会升级为轻量级锁,两个线程公平竞争,哪个线程先占有锁对象,锁对象的Mark Word就指向哪个线程的栈帧中的锁记录。这时偏向锁标识位是0,锁状态是00;

  当锁处于偏向锁,又被另一个线程企图抢占时,偏向锁就会升级为轻量级锁。企图抢占的线程会通过自旋的形式尝试获取锁,不会阻塞抢锁线程,以便提高性能。

  自旋原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要进行内核态和用户态之间的切换来进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免了用户线程和内核切换的消耗。

  但是,线程自旋是需要消耗CPU的,如果一直获取不到锁,那么线程也不能一直占用CPU自旋做无用功,所以需要设定一个自旋等待的最大时间。JVM对于自旋周期的选择,JDK 1.6之后引入了适应性自旋锁,适应性自旋锁意味着自旋的时间不是固定的,而是由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定的。线程如果自旋成功了,下次自旋的次数就会更多,如果自旋失败了,自旋的次数就会减少。

  如果持有锁的线程执行的时间超过自旋等待的最大时间仍没有释放锁,就会导致其他争用锁的线程在最大等待时间内还是获取不到锁,自旋不会一直持续下去,这时争用线程会停止自旋进入阻塞状态,该锁膨胀为重量级锁。

  (4) 重量级锁状态:

  重量级锁会让其他申请的线程之间进入阻塞,性能降低。重量级锁也叫同步锁,这个锁对象MarkWord再次发生变化,会指向一个监视器对象,该监视器对象用集合的形式来登记和管理排队的线程。这时偏向锁标识位是0,锁状态是10;

  

 

  

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注盛行IT的更多内容!

 

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: