java所线程_内置锁02_内置锁和对象结构

Hello,在java多线程编程中,我们经常使用synchronized来进行线程访问控制,那么它底层究竟是通过什么来实现的了?这里我们就对此一探究竟。
在了解内置锁之前,我们必须先来了解一下JAVA的对象结构,这个至关重要。

java 对象结构

1Java对象结构包括三个部分,对象头、对象体和对象对齐字节。
2、普通对象在JVM堆空间中,而Class 对象则存在于JVM方法区中。
3java的对象头又包含 Mark WordClass PointArray Length(可选)

1、对象头
Mark Word :包含GC标记位置、哈希码、锁状态等信息,synchronized 正是通过Mark Word中的锁状态去完成的。
Class Point:类指针,执行方法区中存放class对象的地址。
Array Length:数组长度,如果对象是一个Java数组,则此字段有值,否则此字段为空。
2、对象体
主要包含对象的成员变量,也包括父类的成员变量。
3、对齐字节
对齐字节也叫做填充字节,用来保证JAVA对象所占用的内存字节数为8的倍数,HotSpot 虚拟机要求JAVA对象的起始地址必须是8的倍数。


上面我们了解了JAVA的对象结构,很明显。要学习synchronized底层原理,我们关注的重点当然是Mark Word 的结构。

Mark Word 字段的结构和Java内置锁的状态强相关,这里我们只是大概了解一下其结构。

可以看到,GC垃圾回收的分代年龄,还有对象的hashcode都保存在Mark Word中。至于什么是轻量级锁、偏向锁这些我们将在下面详细说明

内置锁

在JDK1.6版本之前,所有的JAVA内置锁都是重量级锁,重量级锁会造成cpu在用户态和内核态之间频繁切换,代价比较高;JDK1.6版本为了减少内置锁带来的性能消耗,引入了偏向锁和轻量级锁;在JDK1.6版本中内置锁总共有4种状态:无锁、偏向锁、轻量级锁、重量级锁,这些状态随着竞争情况逐渐升级。内置锁可以升级但是不能降级,这意味着偏向锁升级成轻量级锁之后再也不能降级为偏向锁。

内置锁的状态和java对象头的Mark Word紧密相关。且内置锁的升级完全由JVM虚拟机控制,用户完全是无感知的。

无锁状态

无锁状态的Mark word 如下所示:

Lock: 01 锁状态;

Biased: 偏向锁标记,0表示不是偏向锁;

偏向锁状态

偏向锁指的是同步代码一直被同一个线程访问,那么该线程访问同步代码就不用获取锁;偏向指的就是偏爱某个线程。当存在线程竞争时,偏向锁就会自动升级成轻量级锁。

偏向锁状态的Mark Word 如下所示:

Lock: 01 表示锁状态;

Biased: 偏向锁标记,1表示是偏向锁;

线程ID:偏爱线程的ID;

偏向锁是为线程竞争很低的场景设计的,其核心原理是:

  1. JVM发现某个锁90%的时间都是被某个固定线程访问;该线程就会被JVM标记为偏向线程。在对象头Mark Word 中更改偏向标记、记录线程ID(使用CAS操作完成,可以理解为两个步骤拼接为一个原子操作)
  2. 偏向线程再次获取锁的时候,只需要判断偏向线程是否是当前线程,就可以访问同步代码,大大节省了锁环节的时间。

在锁竞争很低的情况下,通过偏向锁当然是可以得到很好的性能的;但是如果锁竞争比较激烈,频繁设置偏向线程。这样就会导致偏向锁性能变低,于是就会自动升级到轻量级锁。

轻量级锁状态

当有少量线程开始竞争这个锁对象的时候,这这些线程占用内置锁的时间又大体不差。这时候偏向锁就会升级为轻量级锁。多个线程公平竞争该锁资源。

轻量级锁状态的Mark Word 如下所示:

Ptr_to_lock_record:指向轻量级锁的指针。

Lock:锁状态为00。

轻量级锁存在的目的是尽可能不动用操作系统层面的互斥锁,因为使用操作系统提供的互斥锁必须要有用户态和内核态的切换,这个是比较消耗性能的。

轻量级锁是通过线程自旋实现的。因此不会涉及到用户态和内核态的切换,其原理大致如下:

  1. 在强锁线程获取锁之前,如果内置锁没有被锁定,JVM首先将会在抢锁线程的栈帧中建立一个锁记录(每个线程都有自己独立的栈帧),用于存储对象目前Mark Word的拷贝。之所以要存储Mark Word的拷贝是为了释放锁的时候恢复Mark Word。
  2. 然后抢锁线程进行CAS自旋操作,将当前栈帧中的Lock Record 写入到Mark Word中,如果写入成功,当前线程就拥有了这个对象锁。

轻量级锁的问题是,当线程较少的时候线程自旋的时间较短,然而一旦竞争的线程增多,则自旋的代价是很大的(cpu使用率会升高);这个时候锁就会自动升级为重量级锁。

重量级锁状态

重量级锁会让其它申请的线程进入阻塞状态,是通过Monitor对象来实现的,而Monitor 对象内部则是通过调用操作系统的互斥锁(Mutex Lock)来实现的。其涉及系统调用,所以性能会相对低一些。

重量级锁状态的Mark Word 如下所示:

Ptr_to_heavyweight_monitor:指向重量级锁的指针。

Lock:锁状态为10。

那么重量级锁究竟是通过Monitor如何实现的了?

monitor直译过来是监视器的意思,专业一点叫管程。monitor是属于编程语言级别的,它的出现是为了解决操作系统级别关于线程同步原语的使用复杂性,类似于语法糖,对复杂操作进行封装。而java 重量级内置锁则基于monitor机制实现了它自己的线程同步机制,


其工作流程大致如下:

1、Owner : 当前那个线程持有该Monitor;
2、EnterList : 排队竞争该锁的线程列表;
3、WaitSet:之前获得过该锁的线程,但条件不满足进入 WAITING 状态的线程。和wait-notify有关系,我们会在线程通信章节详细分析;

字节码角度分析synchronized

上面我们从锁升级和对接结构的角度分析了synchronized的原理,这里我们从字节码的角度再来观察一下synchronized。

首先我们编写一个synchronized相关的类:

 

然后通过javap 工具来查看字节码;
javap -verbose D:\study_note\java_study\code_Java\target\classes\com\java\basic\thread\demo001\Demo004_b.class

从上面可以看出,同步方法jvm是使用ACC_SYNCHRONIZED方法访问标识符实现同步,同步代码块jvm是使用monitorenter和monitorexit指令包裹临界区实现同步。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值