持续创造,加快生长!这是我参加「日新方案 10 月更文挑战」的第29天,点击查看活动详情

Java目标结构

在讲到本文的锁之前,先来简单了解一下Java的目标结构。Java的目标结构首要包括目标头,实例数据,对齐填充三大部分。

偏向锁,轻量级锁,重量级锁的核心原理

目标头

目标头中存储了目标的Mark word,类型指针(元数据指针)和数组长度(只要其时目标为数组目标时才会有)。而Mark word又包括目标的Hashcode码,目标的分代年龄,目标的倾向锁ID,获取倾向锁的时间戳,锁标志位等。

Mark word首要用于存储目标本身运行时的数据,而且Mark word字段的长度与JVM的位数有关,32位的JVM虚拟机中Mark word占用32位的存储空间,64位的JVM虚拟机中占用64位的贮存空间

以32位的JVM虚拟机为例:

偏向锁,轻量级锁,重量级锁的核心原理

是否是倾向锁表明:当值为0时,符号目标没有敞开倾向锁,值为1表明敞开了倾向锁。

锁标志位表明:其时线程具有的锁,不同状况下具有的锁不同。

目标分代年龄表明:当JVM发生GC废物收回时,新生代未被收回的目标在Eden区和Survivor之间的仿制,每次仿制是的分代年龄+1,默许状况下分代年龄到达15会被移到老时代区域,当然这个参数也能够自行设置。

目标的Hashcode值:首要存储目标的Hashcode值。

线程ID:表明在倾向锁状况下,持有倾向锁的线程编号。

指向栈中锁记载的指针:在轻量级锁状况下,指向栈中所记载的指针。

指向(互斥量)重量级锁的指针:在重量级锁状况下,指向目标监视器的指针。

类型指针指向办法区中的类元信息,也叫元数据指针。

关于数组长度,在其时目标是数组类型的时候,目标头中需求额定的空间存储数据长度的信息,假设其时目标不是数组类型的,则不需求。同样,在32位的虚拟机中,数组长度占用32位的存储空间,64位的虚拟机中占用64位的存储空间。

偏向锁,轻量级锁,重量级锁的核心原理

实例数据

实例数据首要存储了目标的成员变量信息,比如成员变量的详细值,父类成员变量的详细值等。

目标填充

在HotSpot虚拟机中,目标的开始地址必须为8的整数倍,假设其时目标的实例变量占用的贮存空间不是8的整数倍,则需求运用填充数据来保证对齐。

倾向锁

为什么会发生倾向锁?在多线程并发履行时,synchronized锁会保证安全性能,但是一同会呈现一个线程多次获取同一个锁的现象(哎,就是玩),因而提出了倾向锁。

倾向锁是怎么作业的?假设在同一时间,只要一个线程拿到了synchronized锁,此刻该线程履行办法或代码块不会呈现与其他线程竞赛的状况,这时,会进入倾向状况。当锁进入倾向状况,目标头中的Mark word就会进入倾向结构,由上文得知,倾向锁符号为1,锁标志位符号为01,而且会把其时拿到锁的线程ID记载到目标头的Mark word中,一旦下次改线程进入临界区(办法或代码块),那么会先检查Mark word中存储的ID和自己的是否共同。

假设发现Mark word中存储的线程ID和自己的共同,则能够进入临界区,假设不共同,阐明有线程与其时拿到锁的线程竞赛。假设线程1正在运用锁资源,线程2发现不共同,则线程2会测验运用CAS机制将目标头Mark word中的线程ID改为线程2自己的,但是又会分为两种状况:

1.当CAS操作履行成功后,表明线程1现已结束了运用锁资源,Mark word中的线程ID会记载为线程2的ID,此刻依然处于倾向锁状况;

2.当CAS操作履行失利,表明线程1依然在占用锁资源,没有开释锁资源,此刻会暂停线程1,而且将Mark word中倾向锁的值设为0,锁标志位设为00,由上文可知,此刻倾向锁会晋级为轻量级锁,当然,线程1和线程2之间会依照轻量级锁的方式来竞赛锁。

倾向锁会提升程序的履行性能,但是倾向锁的吊销晋级是比较复杂的,而且会耗费资源和性能。

偏向锁,轻量级锁,重量级锁的核心原理

轻量级锁

轻量级锁概念是在线程竞赛不是很激烈时,能够经过CAS机制来竞赛锁,防止运用操作系统层面的Mutex重量级锁然后影响性能。

轻量级锁怎么完成呢?当线程被创建后,虚拟时机在线程的栈帧中创建一个用于存储锁记载的空间–Displaced Mark word,关于轻量级锁,在争抢锁资源的线程进入synchronized修饰的办法或代码块之前,会将锁目标中目标头里的Mark word仿制到其时线程的Displaced Mark word空间中,然后,线程会测验运用CAS自旋的方式将锁目标中的Mark word替换成指向锁记载的指针,替换成功则代表其时线程拿到了锁,之后虚拟时机把Mark word中的锁标志位设为00,表明其时为轻量级锁状况,其时线程获取到锁之后,JVM虚拟时机把锁目标中Mark word中的信息保存到获取到的锁资源的线程的栈帧Displaced Mark word中,而且把线程中的owner指针指向锁目标。

当线程抢占到锁资源后,会将锁目标的Mark Word中的信息保存到其时线程栈帧中Displaced Mark World区域,而且锁目标的Mark Word信息也会发生变化,由之前的存储目标的HashCode码到现在变成存储指向栈中锁记载的指针。当线程开释锁时,会测验运用CAS操作将Displaced Mark Word中存储的信息仿制到锁目标中的Mark Word中,假设没有发生锁竞赛,则代表仿制成功,线程会开释锁,假设此刻由于其他线程多次履行CAS操作导致轻量级锁晋级为重量级锁,则其时线程的CAS操作会失利,此刻会开释锁并唤醒其他未获得锁而被堵塞的线程一同竞赛锁。

偏向锁,轻量级锁,重量级锁的核心原理

重量级锁

重量级锁首要基于操作系统中的Mutex锁完成的,重量级锁的履行功率比较低,处于重量级锁时被堵塞的线程不会耗费CPU资源。 它的底层是经过Monitor锁完成等候,假设其时目标锁的状况为倾向锁或轻量级锁,那么在调用锁目标的wait办法或notify办法,或许核算锁目标的HashCode时,倾向锁或轻量级锁就会胀大为重量级锁。

偏向锁,轻量级锁,重量级锁的核心原理

锁晋级

在多线程一同争抢锁时,或许会由无锁状况慢慢晋级为轻量级锁,重量级锁的状况,晋级进程如下:

在多线程竞赛锁时,虚拟时机检测锁目标头中的Mark Word倾向锁符号是否为1,锁符号位是否为01,假设是的话,则其时锁状况为可倾向状况;

多线程争抢锁资源时,会首先检查Mark Word中存储的是不是自己线程的ID,假设是自己线程的话,则现已处于倾向锁状况了,其时的线程能够直接进入办法或代码块中;

假设存储的不是自己的线程,那么其时竞赛锁的线程会运用CAS自旋机制来竞赛锁,假设竞赛成功,则会把Mark Word中存储的线程ID改为其时竞赛锁成功的ID,而且把倾向锁符号设为1,锁标志位设为01,此刻锁状况依然处于倾向锁状况;假设其时线程经过CAS自旋操作竞赛失利,则阐明有其他线程也在争抢锁资源,那么此刻会吊销倾向锁,将倾向锁晋级为轻量级锁;

其时线程通知锁目标的Mark Word中存储的线程ID暂停线程,对应的线程会将Mark Word的内容变为空;

前次拿到倾向锁资源的线程(线程1),和其时争抢锁的线程(线程2)都会把锁目标中的HashCode值等信息仿制到自己栈帧中的Displaced Mark Word中,之后线程1和线程2开始履行CAS自旋操作,测验把锁目标中的Mark Word的内容修改为指向自己线程的Displaced Mark Word的空间来竞赛锁资源;

竞赛成功的线程会拿到锁资源,而且竞赛成功的锁的线程会把锁目标中的Mark Word的内容修改为指向自己线程的Displaced Mark Word的空间,并将Mark Word中的锁标志为设为00,进入轻量级锁状况;

其时竞赛失利的锁不会灰心的,依然会持续CAS自旋的操作来竞赛锁资源,此刻又会分为两种状况:假设竞赛成功,则会拿到轻量级锁,注意是轻量级,此刻锁依然会处于轻量级锁状况;假设竞赛失利,线程一直进行CAS自旋操作到达一定的次数依然没有拿到锁资源,那么轻量级锁会胀大为重量级锁,将Mark Word中的锁标志位设置为10进入重量级锁状况。

因而,综上所述,当同一时间,只要一个线程竞赛锁时,此刻会处于倾向锁状况;假设有多个线程一同竞赛锁时,倾向锁会晋级为轻量级锁状况;当线程以CAS自旋机制超过了一定的自旋次数,依然没有获取到锁,会由轻量级锁晋级为重量级锁状况。

偏向锁,轻量级锁,重量级锁的核心原理

锁消除

只要Java虚拟机敞开了逃逸分析,才会呈现锁消除的现象,意思是当一个目标只能从一个线程被访问到,不存在同享数据的竞赛,在访问这个目标时,能够不加同步锁,假设运用了synchronized同步锁,则虚拟时机主动将synchronized同步锁消除,常见的例子有StringBuffer拼接字符串:

public static String add(String str1, String str2) {
        StringBuffer sb = new StringBuffer();
        sb.append(str1);
        sb.append(str2); 
        return sb.toString();   
        }

StringBuffer中的append()办法源码:

    public synchronized StringBuffer append(StringBuffer sb) {
        toStringCache = null;
        super.append(sb);
        return this;
    }

上述代码中,总所周知StringBuffer是线程安全的,其办法被synchronized锁修饰,尽管存在锁,但是在履行上述代码时,其他线程无法访问到同享数据,因而虚拟时机将synchronized锁安全消除。

偏向锁,轻量级锁,重量级锁的核心原理

本篇文章就分享到这儿了,后续将会分享各种其他关于并发编程的知识,感谢大佬认真读完支持咯 ~

偏向锁,轻量级锁,重量级锁的核心原理

文章到这儿就结束了,假设有什么疑问的地方请指出,诸佬们一同评论 希望能和诸佬们一同尽力,往后进入到心仪的公司 再次感谢各位小伙伴儿们的支持