锁在java中锁住的是什么呢?

461 阅读4分钟

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

前言

锁在 java 项目开发中是一个重要的内容,具体分为 java 关键字锁、java 锁、分布式系统中的分布式锁。也许大家对如何使用锁已经烂熟于心,但是有没有在编码的过程中考虑过这样一个问题,这个锁到底锁住的是什么东西呢?

锁的使用

在开始分享之前,先简单看一下三种锁的实现方式:

// 1 synchronized关键字锁
Object obj = new Object();

synchronized(obj){
   // todo some business
}

// 2 java 锁
ReentrantLock lock = new ReentrantLock();
try {
	if(lock.tryLock()){ // 加锁
		// todo some business
	}
}catch (Exception e){
	// todo ex information
} finally {
	  lock.unlock(); // 无论如何要解锁
}

// 3 分布式锁,通常使用redis来实现
long value = Thread.currentThread().getId();
boolean flag = RedisUtils.set(key,value,"NX",10);
try {
	if(flag)){ // 加锁
		// todo some business
	}
}catch (Exception e){
	// todo ex information
} finally {
  // 解锁时需要判断value值是否为自身
	long val = RedisUtils.get(key);
  if (val - value == 0){
     RedisUtils.del(key);
  }
}

我们通过以上的示例,可以看到不论是何种方式,都需要在一个中心获取一个许可,在单体项目中,就需要获取当前系统中的一个许可,在分布式系统中需要在 redis 中设置一个标识,获取一个许可才能进行后续的操作。对于分布式锁和 java 锁,大家都可以理解,但是 java 关键字的锁,到底锁住的是什么呢,一个关键字在加锁和解锁的过程中起了什么作用,这是需要关注的重点。先说一下结论,java 关键字 synchronized 锁在加锁的过程中,锁住的是 java 对象的对象头。理解这一点,需要先理解一下 java 的对象结构。

java 对象的结构

java 对象的结构

java 对象的结构,在《深入理解 java 虚拟机》一书中有详细的说明,这里直接展示结论:

  • 1 对象头: 对象头由关键字(mark word)和类指针(klass pointer) 两部分组成,如果是数组还要包括数组长度(length)。
  • 2 示例数据: 示例数据就是 java 对象中的属性和方法所属数据。
  • 3 对象填充:java 文件在编译后需要交由 java 虚拟机进行解释生成内存中的数据对象,在 jvm 中需要按照一定的规则读取数据,简单的说就是对象的大小必须是 8byte 的整数倍,不足的部分就需要空白进行补齐。

java 的内存布局也是可以进行展示的,如果需要了解的,可以使用 JOL 查看对象在内存中的分布情况,这里就不再展开了。

java 对象头

关键的内容是对象头的信息,也是和锁直接相关的部分,如下图所示:

synchornized 关键字在 jdk1.6 之后进行了优化,优化的内容就是不再一开始就直接使用重量级锁,而是根据所对象的情况进行判断,从无锁、偏向锁、轻量级锁,最后才升级为重量级锁。如上图所示,每一行都是对象头,但是在锁升级的过程中其结构是不一样的(age:4,这个 4 说的是对象年龄所在的位数,最大也就是 15),在轻量级锁时指向的是锁记录(lock_record),重量级锁时指向的是(monitor),具体的锁状态需要由 biased_lock 偏向锁标识和 lock 锁标记来配合展示,如下图所示:

Synchronized 加锁过程

在锁升级和锁撤销的过程中,需要遵循以下的经典图解:

  • 1 在加锁之前,会先查询对象头的偏向锁标记(biased_lock),如果该值为 0 则在此之前没有锁竞争的情况,将偏向锁的标记置为1,同时将该线程的 threadId 设置到对象头中,此时对象就被锁定了。如果偏向锁的标记为 1,表示已经存在偏向锁,需要在此判断对象头的 thradId 是否为自身,如果是自身则继续操作,不是自身的话,则需要进行锁升级,在锁竞争的阶段,则需要升级为轻量级锁。
  • 2 在轻量级锁阶段偏向锁的标志位0,锁标记为00,此时会在线程的占空间中开辟一块空间,记为锁记录并使用 CAS 将对象头中除了锁信息之外的部分设置到锁记录中,此时完成了轻量级锁的操作。
  • 3 此时如果还有线程需要获得执行权,会进一步的引发锁膨胀为重量级锁,此时锁标记为 10,此时为重量级锁,和轻量级锁类似,需要将对象头的其它信息监视器中,通过指针进行引用。
  • 4 如果锁标记位是 11, 表示对象处于 GC 的状态,这里急需要说一下锁只能升级,不能降级。

总结

本文介绍了编程中常用的锁使用方式,使用提出问题的方式逐层递进引出 java 对象头结构信息,最后解释 java 关键字锁的实现原理,后续会持续更新关于锁的知识,欢迎大家多多关注。