synchronized 之谜

2024-04-24 06:28
文章标签 synchronized 之谜

本文主要是介绍synchronized 之谜,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

序言

本文给大家介绍一下 synchronized 关键字的部分原理。

一、内存中的 Java 对象

class A {private String attr;
}

先引入一个问题:上面类 A 有一个属性 attr。当类 A 实例化之后的对象在内存中是如何表示的呢?

workspace.png

在内存中,Java 对象由三部分组成。其中包括对象头(Header)、实例数据(Instance Data)和对齐填充 (Padding)等部分。

  1. 对象头(Object Header):对象头存储了对象的元数据信息,比如哈希码、锁状态、垃圾回收标记等。对象头的大小在 32 位和 64 位的 JVM 上可能会有所不同,通常占用 8 字节或更多的空间。
  2. 实例数据(Instance Data):实例数据是对象的成员变量(字段)的实际存储区域。每个字段根据其类型占用不同的内存空间。例如,一个 int 类型的字段占用 4 字节,一个对象引用占用 4 字节(在 32 位 JVM 上)或 8 字节(在 64 位 JVM 上)。
  3. 对齐填充(Padding):由于硬件要求数据在内存中的地址是对齐的,因此在实例数据和对象头之间可能存在一些填充字节,以保证对象的起始地址是对齐的。填充的大小通常是对象头和实例数据大小的倍数,以满足对齐要求。

二、Java 对象头

对象头包含了两部分,分别是运行时元数据(Mark Word)和类型指针(Klass Word)。

workspace (1).png

在 32 位的虚拟机中,对象头占 64 位。其中 Mark Word 占 32 位,Klass Word 占 32 位。

workspace (2).png

在 64 位的虚拟机中,对象头占 128 位。其中 Mark Word 占 64 位,Klass Word 占 64 位。
接下来我们以 64 位的虚拟机为例。

三、Mark Word

对象头的 Mark Word 存储了对象的元信息,其中包括了对象的锁状态、GC(垃圾回收)相关信息等。 Mark Word 中通常用来表示对象的锁状态的部分称为锁标志位 (Lock Word),它包含了对象的锁状态、线程 ID 等信息。

通常情况下,锁标志位可以有以下几种状态:

  1. 无锁状态:对象尚未被锁定,可以被任意线程访问。
  2. 偏向锁状态:对象已经被某个线程锁定,但是尚未涉及竞争。在这种状态下,MarkWord 中会记录拥有锁的线程 ID,并且对象的锁标志位中会设置偏向锁标志。
  3. 轻量级锁状态:多个线程竞争同一个锁,但尚未涉及到真正的阻塞,因此采用了一种轻量级的锁机制来进行竞争。在这种状态下,MarkWord 中会记录锁的指针,用于指向锁记录(Lock Record),并且对象的锁标志位中会设置轻量级锁标志。
  4. 重量级锁状态:当轻量级锁竞争不过,会升级为重量级锁,这时会涉及到阻塞和唤醒线程的操作。在这种状态下,MarkWord 中不再存储锁记录的指针,而是直接指向锁对象,并且对象的锁标志位中会设置重量级锁标志。
  5. GC 标志位:有些 JVM 实现中,MarkWord 还可能包含 GC 相关的标志位,用于标记对象是否被回收等信息。

3.1 无锁状态的 Mark Word

workspace.png

上图是 64 位虚拟机中无锁状态的 Mark Word。表示无锁是 Mark Word 的后三位(Mark Word 后三位 001 表示无锁),即:baised_lock 标志位是 0,Mark Word 最后两位是 01。

3.2 偏向锁状态的 Mark Word

workspace (1).png

上图是 64 位虚拟机中偏向锁状态的 Mark Word。表示偏向锁是 Mark Word 的后三位(后三位 101 表示偏向锁),即:baised_lock 标志位是 1,Mark Word 最后两位是 01。

3.3 轻量级锁状态的 Mark Word

workspace.png

上图是 64 位虚拟机中轻量级锁状态的 Mark Word。后两位 00 表示轻量级锁。

3.4 重量级锁状态的 Mark Word

workspace.png

上图是 64 位虚拟机中重量级锁状态的 Mark Word。后两位 10 表示轻量级锁。

3.5 GC 标志位的 Mark Word

workspace (1).png
上图是 64 位虚拟机中 GC 标志位的 Mark Word。后两位 11 表示 GC 标志位。

四、synchronized 工作流程

class A {private String attr;public void setAttr(String attr) {// 使用 synchronized 加锁synchronized (this) {this.attr = attr;}}
}

在上述代码中,setAttr() 方法中使用了 synchronized,其中锁住的是当前对象 A。

  1. 当使用 synchronized 锁住对象 A ,对象 A 未被使用时,对象 A 的 Mark Word 依旧没有改变(即 Mark Word 后三位是 001,无锁状态)。
  2. 当第一次有一个线程去访问对象 A 时,此时 Mark Word 的锁标志位会变成 101(即表示对象现在使用的偏向锁)。
  3. 若后面有少量其他线程也去获取对象 A 的锁,对象 A 会先撤销偏向锁,偏向锁撤销成功则尝试采用 CAS 加上轻量级锁,轻量级锁加锁成功则将 Mark Word 的锁标志位变成 00(即表示对象现在使用的轻量级锁)
  4. 若后面有更多的线程前来争抢对象 A 的锁,其他未抢到锁的线程就会发生 CAS 自旋。自旋超时之后,系统便判断当前对于该锁的竞争非常激烈,将会撤销轻量级锁,然后加上重量级锁,并将锁标志位相应地更新为重量级锁即 10

在这里插入图片描述

通过上面的分析,我们知道 synchronized 在底层存在一种锁升级机制而不是使用一种固定的锁。这种锁升级机制是根据不同的并发度使用不同的加锁方式(这也是 Java 团队对 synchronized 的优化)。

当我们使用 synchronized 关键字时,一般就认为该部分代码会在多线程环境中执行。如果对象从无锁状态一步一步升级会浪费性能。所以,通常 JVM 会开启偏向锁,即对象创建之后 Mark Word 的后三位是 101 而不是 001。

往期推荐

  1. 为什么 MySQL 单表数据量最好别超过 2000w
  2. ConcurrentHashMap 源码分析(一)
  3. IoC 思想简单而深邃
  4. ThreadLocal
  5. JDK 动态代理

这篇关于synchronized 之谜的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/930990

相关文章

关键字synchronized、volatile的比较

关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字的执行效率上得到很大提升,在开发中使用synchronized关键字的比率还是比较大的。多线程访问volatile不会发生阻塞,而synchronize

面试官:synchronized的锁升级过程是怎样的?

大家好,我是大明哥,一个专注「死磕 Java」系列创作的硬核程序员。 回答 在 JDK 1.6之前,synchronized 是一个重量级、效率比较低下的锁,但是在JDK 1.6后,JVM 为了提高锁的获取与释放效,,对 synchronized 进行了优化,引入了偏向锁和轻量级锁,至此,锁的状态有四种,级别由低到高依次为:无锁、偏向锁、轻量级锁、重量级锁。 锁升级就是无锁 —>

【Java编程的思想】理解synchronized

用法和基本原理 synchronized可以用于修饰类的实例方法、静态方法和代码块 实例方法 在介绍并发基础知识的时候,有一部分是关于竞态条件的,当多个线程访问和操作同一个对象时,由于语句不是原子操作,所以得到了不正确的结果。这个地方就可以用synchronized进行处理 public class Counter {private int count;public synchroni

【大数据Java基础- Java并发 20】深入分析synchronized的实现原理

记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized,相对于当时的我们来说synchronized是这么的神奇而又强大,那个时候我们赋予它一个名字“同步”,也成为了我们解决多线程情况的百试不爽的良药。但是,随着我们学习的进行我们知道synchronized是一个重量级锁,相对于Lock,它会显得那么笨重,以至于我们认为它不是那么的高效而慢慢摒弃它。 诚然,随着Javs S

java线程 yield,sleep,join,synchronized wait notify notifyAll,ReentrantLock lock condition, 生产者消费者

yield,sleep,join yield,join,sleep,join是Thread中的方法,不需要 在synchronized 代码块中调用,和synchronized 没关系,也不会释放锁。 Thread.sleep(100);Thread.yield();Thread t;t.join(); (1)yield()不一定保证让出cpu yield()只是使当前线程重新回

java线程锁 synchronized

//java锁是对同一个对象或者同一个对象中的方法加锁;关键是同一个 错误的加锁方式 public class MyWaitNotify { public static void main(String[] args) { MyT m1=new MyT("A"); MyT m2=new MyT("B"); m1.start(); m2.start(); } } class MyT

java synchronized原理与 为何锁升级及过程

关于锁升级 java1.6之前Syntronized 没有锁升级概念,只有重量锁:即用户态和内核态的上下文切换 会比较浪费时间。 java1.6之后,Syntronized关键字 开始有锁升级的概念,即偏向锁,轻量级锁,重量级锁。   注意CAS不是自旋锁,(CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。)

多线程 | synchronized的简单使用

synchronized 关键字是 Java 中解决并发问题的一种常用方法,也是最简单的一种方法,其作用有三个: (1)互斥性:确保线程互斥的访问同步代码 (2)可见性:保证共享变量的修改能够及时可见 (3)有序性:有效解决重排序问题, 其用法也有三个: 修饰实例方法修饰静态方法修饰代码块 修饰实例方法 public class SynchronizedInstanceMethod impl

线程同步(synchronized关键字)

概念 解决数据共享问题,必须使用同步,所谓同步就是指多个线程在同一时间段内只能有一个线程执行代码,其他线程要等待此线程完成之后才可以继续执行。 方法 线程进行同步,有以下两种方法: (1)同步代码块 synchronized(要同步的对象){ 要同步的操作; } (2)同步方法 public synchronized void method(){ 要同步的操作; } 同步代

Java线程同步机制Synchronized

同步机制的基本概念         想象一下,你和几个朋友一起去抢购限量版的商品。如果大家同时抢购,可能会导致混乱,比如两个人同时拿到最后一件商品,或者有人多拿了。为了避免这种情况,你们可以约定一个规则:每次只有一个人可以拿商品,其他人必须等待。这就是同步机制的基本思想——确保在同一时间只有一个线程可以访问共享资源。 Synchronized关键字的作用   synchronized关键字就