synchronized的底层原理

本文深入探讨了Java中synchronized关键字的使用场景、方法和底层实现原理。解释了synchronized如何解决并发编程中的线程安全问题,包括其作用于实例方法、静态方法和代码块的不同方式。同时,详细解析了synchronized的底层实现,从MarkWord到Monitor的锁升级过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 为什么用synchronized
在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以访问特定的代码,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。
2 怎么用synchronized
1 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
2 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
3 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
3 synchronized底层实现
底层利用对象头的Mark Word(标记字符)标记锁的状态,实现1.6以后synchronized锁的由无锁–偏向锁–轻量级锁–重量级锁状态的升级;利用monitor(监视器)来获取锁,实现同步。
对象在内存中的布局
我们都知道,Java对象存储在堆(Heap)内存。括起来分为对象头、对象体(实例数据)和对齐字节((对象填充)。如下图所示:
在这里插入图片描述
Java对象头
对象头由两部分组成,一部分用于存储自身的运行时数据,称为 Mark Word,另外一部分是类型指针,及对象指向它的类元数据的指针。
对象头 = Mark Word + 类型指针
1.对象头中的Mark Word(标记字)用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,在Java对象头中有2bit用来标识对象存储锁的,比如jdk1.6以后synhronized通过改变锁状态实现对锁的升级和优化。
2.Klass Word是一个指向方法区中Class信息的指针,意味该对象可随时知道自己是哪个Class的实例;
3.数组长度也是占用64位(8字节)的空间,这是可选的,只有当本对象是一个数组对象时才会有这个部分

对象体(实例数据)
实例数据部分是对象真正存储的有效信息, 也是在程序代码中所定义的各种类型的字段内容

对齐字节(对象填充)
在64位JVM内存管理系统中要求对象起始地址必须是8字节的整数倍,就是对象的大小必须是8字节的整数倍,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
在IDE的项目中引入openJdk的jar包可以打印出java对象头详细信息

<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-cli -->
		<dependency>
		    <groupId>org.openjdk.jol</groupId>
		    <artifactId>jol-cli</artifactId>
		    <version>0.9</version>
		</dependency>

测试的代码实例

public class Layout {
	static Student stu = new Student();
	public static void main(String[] args) {
		System.out.println("start");
		//打印对象头信息
		System.out.println(ClassLayout.parseInstance(stu).toPrintable());
		synchronized (stu) {
			System.out.println("lock ing");
		}
		System.out.println("end");	
	}
}
public class Student {
	Integer age;     //年龄
	Integer clazz;   //班级
}

运行打印的结果
在这里插入图片描述
monitor
每个对象都有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态。当java代码里用synchronized关键字修饰时,通过javap反编译可查看含有monitorenter和monitorexit等指令,案例如下:

public class DiCeng {
    public static void main(String[] args)
    {
        Object obj=new Object();
        synchronized (obj)  //把obj对象锁住
        {
            System.out.println("hello sun");
        }
    }
}

先将Java程序编译:javac -encoding UTF-8 DiCeng.java ;
然后进行反编译:javap -c DiCeng ;显示如下:
在这里插入图片描述
当一个线程进进入用synchronized关键字修饰的方法或代码块的时候,要想访问受保护的数据(即需要获取对象的Monitor)时,先通过monitorenter命令进行加锁,持有该对象的Monitor,当退出同步方法或同步代码块时,执行monitorexit命令解锁释放Monitor。此时其他的线程可以恢复访问。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值