目录
并发编程之 Synchronized 关键字底层原理与锁升级
在并发编程中,synchronized关键字是实现线程安全的重要手段,其底层原理涉及到多种锁机制以及锁升级的概念。
一、synchronized关键字与monitor
Synchronized关键字在底层是通过monitor实现的。monitor是 JVM 提供的、用 C++ 实现的一种机制。当线程使用synchronized关键字时,线程需要关联monitor,这个过程会涉及到用户态和内核态的切换。用户态指的是我们编写的 Java 代码执行的状态,内核态则是 CPU 层面的状态,二者资源权限不同,用户态权限低,内核态权限高。由于 JVM 是系统级别的,属于内核态,而线程受 JVM 管理,这种切换成本较高,并且存在进程上下文切换,导致monitor这种锁机制的性能相对较低,所以monitor实现的锁被称为重量级锁。
二、锁升级
在 JDK1.6 之后,引入了偏向锁和轻量级锁,这是为了在不同的场景下提高性能。
(一)偏向锁
当一个线程获取锁,并且在很长一段时间内,只有这一个线程重复获取该锁(没有其他线程竞争)时,使用偏向锁可以提高性能。例如:
class MyClass {
private Object lock = new Object();
public void method1() {
synchronized(lock) {
// 这里是同步代码块
}
}
public void method2() {
synchronized(lock) {
// 这里是同步代码块,和 method1 使用同一个锁对象
}
}
}
在上述代码中,如果一个线程先调用method1,然后调用method2,都是对同一个lock对象加锁,这种情况下就适合使用偏向锁。
偏向锁在对象头的mark word中有特殊的记录方式。在 32 位虚拟机中,mark word里有线程 ID(23 位)、偏向锁的时间戳(2 位),偏向锁标识(1 位),当标识为 1 且后三位是 101 时,表示是偏向锁。当一个线程第一次获取锁时,会通过 CAS 操作将自己的线程 ID 等信息写入mark word,之后该线程再次获取锁时,只需判断mark word中的线程 ID 是否是自己,不需要再进行 CAS 操作。
(二)轻量级锁
当多个线程获取锁,但获取锁的时候没有竞争,或者是同步代码块中的代码不存在竞争,线程是交替执行的情况,使用轻量级锁更合适。例如:
class MyClass {
Object obj = new Object();
public void method1() {
synchronized(obj) {
// 同步代码块内容
}
}
public void method2() {
synchronized(obj) {
// 同步代码块内容,和 method1 使用同一个对象锁
}
}
}
当一个线程执行method1时,会创建一个锁记录(local record),这个锁记录包含指向锁对象的指针、锁记录地址等信息。然后通过 CAS 操作将对象的mark word数据与锁记录的数据进行交换,如果交换成功,对象头就存储锁记录地址和表示该线程拥有锁的状态(00)。如果 CAS 操作失败,可能是有多个线程竞争,此时会升级为重量级锁;也可能是锁重入,会在栈帧中再添加一条local record作为重入计数。当线程退出同步代码块时,如果锁记录不为空,会通过 CAS 操作将数据交换回来完成解锁。
(三)重量级锁
当锁发生竞争时,就会使用重量级锁(monitor)来解决。在重量级锁中,对象锁是通过在对象头的mark word中记录monitor的地址,从而与monitor关联起来。例如,在 32 位虚拟机中,mark word的最后两位是 10 时,表示是重量级锁,前面的指针指向monitor系统。
三、Java 对象的内存结构与锁的关联
在 JVM 的内存结构中,创建的对象都在堆中。以 HotSpot 虚拟机为例,对象分为三个部分:对象头、实例数据和对齐填充数据。
(一)对象头
对象头又分为mark word和class word。mark word与锁机制密切相关,它记录了对象与monitor的关联方式、锁的状态等信息。class word描述的是对象实例的具体类型。
(二)实例数据
就是类的成员变量,比如User类中有name和age等成员变量,实例数据存储的就是这些成员变量的数据。
(三)对齐填充数据
如果对象头和实例变量占用的内存不是 8 的整数倍,则需要一些无意义的数据进行填充,使内存起始地址为 8 的倍数,这是 HotSpot 虚拟机内存管理系统的要求。
通过理解synchronized关键字底层的锁机制以及 Java 对象内存结构与锁的关联,可以更好地掌握并发编程中的线程安全问题,从而编写出更高效、稳定的多线程代码。无论是后端的 Java 代码还是前端的 Vue 代码,在涉及到多线程相关的功能实现时,这些知识都非常关键,而 Python 代码在其他相关并发场景中也可以借鉴这些原理。

1067

被折叠的 条评论
为什么被折叠?



