【一文读懂】 Java并发 - 锁升级原理

简介: Java对象头,锁升级的原因,重量级锁、轻量级锁、偏向锁的原理

要明白锁的原理,首先要知道对象头

Java对象头

在Java中,一个对象一般由两部分组成 :1、对象头 ; 2、对象的成员变量信息

在32位的虚拟机中:

(1)普通对象的对象头长度64bit(8字节):
其中的32bit是Mark Word,另外32位是Klass Word,如下:
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------|-------------------------------------|

(2)数组对象的对象头长度96bit(12自己):
除了Mark Word和Klass Word,还有32bit的 array length,如下:
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|------------------------|

什么是 Mark Word 什么是 Klass Word 什么是array length ?

  • array length - 很明显这是标明数组长度的字段
  • Klass Word - 对象类型指针,通过该字段标明当前对象所属的类
  • Mark Word - 下面着重介绍

在不同状态下,Mark Word 会有不同的表现形式,对应关系如下:

Normal 就是对象的正常状态,以该状态为例

  • 25位是对象的hashcode
  • 4位是age,在垃圾回收中的分代年龄
  • 1位biased_lock,标明是否是对象锁
  • 2位(01),表明当前的加锁状态

Biased 是偏向锁状态
Lightweight Locked 是轻量级锁状态
Heavyweight Locked 是重量级锁状态
Marked for GC 是在JVM进行GC时的状态

无论是偏向锁、轻量级锁、重量级锁,都是在对象头中的Mark Word做文章

锁升级

在默认情况下,synchronized 会首先使用偏向锁,当发现存在竞争时,就会升级为轻量级锁,当竞争比较激烈时,轻量级锁就会升级为重量级锁
但是我们可以通过添加 VM 参数 -XX:-UseBiasedLocking 手动关闭偏向锁,那么synchronized 会直接进入轻量级锁

为什么会有锁升级?

因为从偏向锁 - 轻量级锁 - 重量级锁,互斥程度会越来越高,但是性能代价也越来越大,所以在一开始,如果资源竞争不是很激烈,应该优先选择程度较轻的锁,以下是《Java并发编程的艺术》一书中的原话

Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在
Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状
态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏
向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高
获得锁和释放锁的效率,下文会详细分析。

重量级锁原理

操作系统会提供 Monitor 对象,对某个对象加上重量级锁之后,该对象头中的 Mark Word 中就被设置成指向 Monitor 对象的指针 即上图所示的 ptr_to_heavyweight_monitor:30

在Monitor对象中会有两个属性: 1、Owner ;2、EntryList
Owner - 指当前这个对象的锁是由哪个对象持有的
EntryList - 是一个线程队列,当其他线程来竞争该对象锁失败时,就会进入EntryList 中阻塞

因为重量级锁的加锁过程需要使用到操作系统提供的 Monitor 对象,换言之,属于内核级别,因此性能代价是较大的,如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化

重量级锁的自选优化

重量级锁竞争的时候,可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),当前线程就可以避免阻塞

轻量级锁

每个线程的每个栈帧都会包含一个锁记录的结构

锁记录包含两个部分,其中一个部分用存储上锁对象的 Mark Word,另外一部分是对象指针 Object reference,用于指向上锁对象的地址

上锁过程如下,让锁记录中 Object reference 指向上锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录

如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁,结果如下

如果cas 失败,有两种情况:

  • 其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
  • 自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数,如下:

解锁时有如下两种情况

  • 锁记录的值为 null,表示有重入,这时重置锁记录,表示重入计数减一
  • 锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头,如果失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

锁膨胀过程

假设此时Thread-0已经对某个对象上了轻量级锁,现实Thread-1也尝试对改对象加锁,必然加锁失败,进入锁升级

  1. 即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址
  2. 然后自己进入 Monitor 的 EntryList 阻塞

如下图

当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 Entry List 中 的Thread-1 线程

轻量级锁缺点

在很多情况下,一个对象在某段时间内,会不停被同一个线程上锁,即不断发生锁重入,这就会频繁的进行Mark Word的cas操作,这也是存在一定性能损耗的,因此诞生了偏向锁来优化

偏向锁

只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有

比较Normal状态和偏向锁状态的Mark Word:

我们发现Normal状态下 biased_lock 是0,偏向锁的 biased_lock 是1; biased_lock 是偏向锁的开启标志,0表示禁用,1表示开启

而且对于Normal,用了 31bit 去存储对象的hashcode;
而偏向锁状态下不在记录对象的hashcode,而是记录线程编号:初始状态下 54bit 的thread标记都是0,当有线程对该对象上轻量级锁后, 54bit 的thread标记记录下当前线程编号,而且那么线程出了临界区释放了锁,thread不会发生改变,所以当该线程对该对象进行锁重入或者重新上锁时,发现里面的线程编号就是自己,就表示没有竞争,不用 CAS

而如果有另外一个线程要对该对象上锁时,发现当前记录着其他线程的编号,就进行锁膨胀,升级为轻量级锁

目录
相关文章
|
10天前
|
Java 程序员 开发者
深入理解Java并发编程:线程同步与锁机制
【4月更文挑战第30天】 在多线程的世界中,确保数据的一致性和线程间的有效通信是至关重要的。本文将深入探讨Java并发编程中的核心概念——线程同步与锁机制。我们将从基本的synchronized关键字开始,逐步过渡到更复杂的ReentrantLock类,并探讨它们如何帮助我们在多线程环境中保持数据完整性和避免常见的并发问题。文章还将通过示例代码,展示这些同步工具在实际开发中的应用,帮助读者构建对Java并发编程深层次的理解。
|
21小时前
|
安全 Java 程序员
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
5 0
|
1天前
|
缓存 Java 数据库
Java并发编程中的锁优化策略
【5月更文挑战第9天】 在高负载的多线程应用中,Java并发编程的高效性至关重要。本文将探讨几种常见的锁优化技术,旨在提高Java应用程序在并发环境下的性能。我们将从基本的synchronized关键字开始,逐步深入到更高效的Lock接口实现,以及Java 6引入的java.util.concurrent包中的高级工具类。文中还会介绍读写锁(ReadWriteLock)的概念和实现原理,并通过对比分析各自的优势和适用场景,为开发者提供实用的锁优化策略。
2 0
|
1天前
|
算法 安全 Java
深入探索Java中的并发编程:CAS机制的原理与应用
总之,CAS机制是一种用于并发编程的原子操作,它通过比较内存中的值和预期值来实现多线程下的数据同步和互斥,从而提供了高效的并发控制。它在Java中被广泛应用于实现线程安全的数据结构和算法。
9 0
|
1天前
|
存储 安全 算法
掌握Java并发编程:Lock、Condition与并发集合
掌握Java并发编程:Lock、Condition与并发集合
8 0
|
2天前
|
Java 编译器 开发者
Java并发编程中的锁优化策略
【5月更文挑战第8天】在Java并发编程中,锁是实现线程同步的关键机制。为了提高程序的性能,我们需要对锁进行优化。本文将介绍Java并发编程中的锁优化策略,包括锁粗化、锁消除、锁降级和读写锁等方法,以帮助开发者提高多线程应用的性能。
|
2天前
|
存储 监控 安全
JVM工作原理与实战(十六):运行时数据区-Java虚拟机栈
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了运行时数据区、Java虚拟机栈等内容。
|
3天前
|
Java
Java并发Futures和Callables类
Java程序`TestThread`演示了如何在多线程环境中使用`Futures`和`Callables`。它创建了一个单线程`ExecutorService`,然后提交两个`FactorialService`任务,分别计算10和20的阶乘。每个任务返回一个`Future`对象,通过`get`方法获取结果,该方法会阻塞直到计算完成。计算过程中模拟延迟以展示异步执行。最终,打印出10!和20!的结果。
|
3天前
|
安全 Java
Java中的并发编程:理解并发性与线程安全
Java作为一种广泛应用的编程语言,在并发编程方面具有显著的优势和特点。本文将探讨Java中的并发编程概念,重点关注并发性与线程安全,并提供一些实用的技巧和建议,帮助开发人员更好地理解和应用Java中的并发机制。
|
4天前
|
搜索推荐 Java Shell
8大Java排序方法(由简入繁),有代码详解和原理指导
8大Java排序方法(由简入繁),有代码详解和原理指导
20 0