扒一扒Synchronized的“底裤”一

编程入门 行业动态 更新时间:2024-10-18 22:28:39

扒一扒Synchronized的“<a href=https://www.elefans.com/category/jswz/34/1660706.html style=底裤”一"/>

扒一扒Synchronized的“底裤”一


       在提到synchronized关键字的时候,可能第一反应就是性能慢,效率低。但是在JDK1.6后,synchronized只想说冤枉呀!

       synchronized在JDK 1.6之前是基于Monitor来进行实现的,Monitor底层又依赖于操作系统的Mutex Lock (互斥锁,借助pthread_mutex_lock函数进行实现) 功能来进行线程的同步功能。因为,它借助操作系统函数实现锁,所以叫做重量级锁。一般只要依赖于操作系统的都属于重量级的。

       但在JDK 1.6之后为了提高性能,减少获取锁和释放锁带来的性能损耗,就引入了 “偏向锁” 和 “轻量级锁” 概念。因此JDK 1.6之后 synchronized 中一共有四种锁的状态:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。它们会随着线程的竞争逐渐的升级。

       并且熟悉它的人知道,在线程进入synchronized代码块时,它锁的是一个对象,而不是锁的整个代码块,注意这一点。

这里就会有一个疑问,它是如何锁定一个对象的呢?

       首先来看一下,一个对象是如何在内存中的进行布局的。在内存中创建一个对象由三大部分组成,分别为对象头、实例数据、对齐填充。其中对象头又由Mark Word、Class Pointer、数组长度组成。

       其中对象头的Mark Word就是用来存储一些锁的信息,后面会详细介绍说。Class Pointer为类型指针,表示当前对象属于哪一个Class类。而数组长度这部分数据,如果创建的对象是一个普通对象那么就不会存在,如果是创建的为一个数组对象那么就会存在。

       实例数据,则存放这个对象的一些成员变量等信息。

       对齐填充,在64位操作系统中,JVM以8字节对齐,不够8字节会自动补齐,目的是为了方便管理,减少内存碎片。

       其中Mark Word用来存储锁的信息,占8个字节 (64 bit) 。它由以下四种状态其中之一组成。

       然后就要用代码来证明Mark Word这些信息确实存在。而OpenJDK提供了一套JOL工具类,可以用来查看对象的内存信息。要想使用要先引入pom文件,接下来就是代码示例。

<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.10</version>
</dependency>
  • 无锁状态
import org.openjdk.jol.info.ClassLayout;public class Test {public static void main(String[] args) {Person person = new Person("A10000", "小废狗");System.out.println(ClassLayout.parseInstance(person).toPrintable());}
}
class Person {private String cartId;private String name;public Person(String cartId, String name) {this.cartId = cartId;this.name = name;}
}

       运行结果如下:

首先,要了解一个基本概念,就是大端存储和小端存储。简单的来说,大端存储就相当于我们正常理解一个数1,2,3,4,5,6这样存储;而小端相当于把这个数倒过来6,5,4,3,2,1这样。知道概念即可,想细致了解可以去查资料。

       其中Mark Word部分占8个字节,并以小端模式输出。如果把小端模式转换为大端模式为,需要把数据进行倒着处理一下。转换后的大端模式下的Mark Word,可以和上图Mark Word的无锁状态进行对比,可以看见最后三位确实是001,表示这个对象为无锁状态。


       class pointer部分在JDK 1.6默认开启指针压缩,占4个字节。设置参数-XX:-UseCompressedOops会关闭指针压缩,class pointer类型指针就会占用8个字节。

       以下就是实例数据部分和对齐填充部分,可以了解一下,主要使用的还是Mark Word部分。



       调用hashcode()方法,设置对象头的hash值。

import org.openjdk.jol.info.ClassLayout;public class Test {public static void main(String[] args) {Person person = new Person("A10000", "小废狗");person.hashCode();System.out.println(ClassLayout.parseInstance(person).toPrintable());}
}

       运行结果如下:

       对象在调用hashcode()函数的时候,就会计算hash值,然后保存到对象头中,下次在调用直接从对象头获取即可。

  • 偏向锁

       其实,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,所以为了让线程获得锁的代价更低故而引入了偏向锁。偏向锁其实就是偏向某一个线程后,就不会在偏向另一个线程了。

偏向锁,主要就是理解“偏向”二字,“偏向”又可以理解为“偏见”的意思。相当于可以理解为对某个人有偏见,倾向于这个人。

人心中的成见是一座大山,任你怎么努力都休想搬动。

       偏向锁就是说,当一个线程第一次访问同步块并获取锁时,会在锁的对象头中存储锁偏向的线程ID。在下次进入同步代码块的时候,只需要判断锁对象头中的线程ID是否指向当前线程。如是,表示已偏向线程可以获得了锁。如果不是,则需要进行锁的升级为轻量级锁。
       偏向锁的优势,就是在于同一个线程在下次进入同步代码块的时候,只需要判断锁对象的中存储的线程ID和当前线程ID是否相等即可。不需要每次都通过操作系统函数来进行实现,降低系统开销。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。

       看一个偏向锁的一个有意思的情况,以下代码就行对比。

程序1:

public class Test {public static void main(String[] args) throws InterruptedException {Thread.sleep(4000);Person person = new Person("A10000", "小废狗");System.out.println(ClassLayout.parseInstance(person).toPrintable());}
}


程序2:

public class Test {public static void main(String[] args) throws InterruptedException {Thread.sleep(4100);Person person = new Person("A10000", "小废狗");System.out.println(ClassLayout.parseInstance(person).toPrintable());}
}







       也可以通过JVM参数 -XX:BiasedLockingStartupDelay=0 关闭延迟开启偏向锁。但是如果创建的对象是无锁状态,在进入同步代码块的时,就会升级为轻量级状态,而不是偏向锁状态。原因是在同步代码块的处理中,会先判断锁对象的是否开启偏向锁,就是倒数第3bit位 (biased_lock)是否为1,因为创建的对象是无锁状态biased_lock为0,所以不会走偏向锁逻辑。因此在进入同步代码块的时候,会从无锁状态升级为轻量级锁。 具体的详细步骤,会在JVM的偏向锁源码里说明。

public class Test {public static void main(String[] args) throws InterruptedException {Person person = new Person("A10000", "小废狗");synchronized (person) {System.out.println(ClassLayout.parseInstance(person).toPrintable());}}
}



       可以发现,匿名偏向锁对象在进入同步代码块后,仍是偏向锁对象,但是和匿名偏向锁对象不同的是,匿名偏向锁对象的线程ID都为0,而进入同步代码块后,偏向锁的线程ID已经存储当前线程的ID了。

import org.openjdk.jol.info.ClassLayout;public class Test {public static void main(String[] args) throws InterruptedException {Thread.sleep(4100);Person person = new Person("A10000", "小废狗");synchronized (person) {System.out.println(ClassLayout.parseInstance(person).toPrintable());}}
}




import org.openjdk.jol.info.ClassLayout;public class Test {public static void main(String[] args) throws InterruptedException {Thread.sleep(4100);Person person = new Person("A10000", "小废狗");person.hashCode();System.out.println(ClassLayout.parseInstance(person).toPrintable());}
}


  • 轻量级锁

       轻量级锁是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。轻量级锁的lock标志为00。

```java
public class Test {public static void main(String[] args) throws InterruptedException {Person person = new Person("A10000", "小废狗");synchronized (person) {System.out.println(ClassLayout.parseInstance(person).toPrintable());}}
}



       在代码进入同步块的时候,如果同步对象锁状态为无锁状态,虚拟机首先将在当前线程的栈帧中创建一块为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,然后拷贝对象头中的Mark Word复制到锁记录中。

       拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock Record里的owner指针指向对象的Mark Word。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,表示此对象处于轻量级锁定状态。

       如果轻量级锁的更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明多个线程竞争锁。

  • 重量级锁

       当出现多线程竞争的时候或者当前线程wait(),会升级为重量级锁时,锁标志的状态值变为“10”。Mark Word中存储的是指向重量级锁的Monitor指针,此时如果有其他的线程都会进入阻塞状态。其中Monitor也存储了锁对象的hash值、age信息等。这些数据在之后的源码里都会体现处理,大概了解一些即可。

import org.openjdk.jol.info.ClassLayout;public class Test {public static void main(String[] args) throws InterruptedException {Person person = new Person("A10000", "小废狗");synchronized (person) {person.wait(1);System.out.println(ClassLayout.parseInstance(person).toPrintable());}}
}


       只是,通过JOL工具证明这四种状态的存在,至于一些细节,会在之后进行讲解。需要分为偏向锁、轻量级锁和重量级锁三章内容。具体会涉及到锁的升级,以及JVM底层源码的讲解。
       关注公众号“程序员秋田君”,领取更多Java课程。
                                                  

更多推荐

扒一扒Synchronized的“底裤”一

本文发布于:2024-02-17 10:44:33,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1693717.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:底裤   扒一扒   Synchronized

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!