type
status
date
slug
summary
tags
category
icon
password
引言
在多线程编程中,线程同步是一个至关重要的概念。Java 提供了多种实现线程同步的机制,其中最常用的之一就是
synchronized
关键字。本文将详细介绍 synchronized
的实现原理,并讨论在实际应用中如何进行锁优化,以提高程序的执行效率。synchronized 的实现原理
synchronized
是 Java 提供的一种内置锁机制,用于确保在同一时刻只有一个线程可以执行同步代码块。它可以用来修饰方法或者代码块,实现对资源的互斥访问。1. synchronized 方法和同步代码块
synchronized
关键字可以用在方法上,也可以用在代码块上。- 同步方法:
synchronized
可以修饰实例方法和静态方法。对于实例方法,锁是加在实例对象上的;对于静态方法,锁是加在类对象上的。
- 同步代码块:
synchronized
还可以用来修饰代码块,这样可以更加精细地控制锁的粒度。
2. Monitor 和锁
synchronized
的底层是基于 Monitor 实现的。Monitor 是一种同步工具,它在 Java 中通过对象的 Mark Word 以及 Monitor 对象来实现。每个 Java 对象都有一个关联的 Monitor 对象,当一个线程进入同步块时,它会尝试获取该对象的 Monitor,如果获取成功,则进入同步块执行;否则,线程会被阻塞,直到 Monitor 被释放。3. JVM 实现
在 JVM 中,synchronized 的实现是通过字节码指令来完成的。主要涉及两条指令:
monitorenter
:当线程进入同步块时,执行该指令。
monitorexit
:当线程退出同步块时,执行该指令。
当编译 Java 代码时,编译器会在同步方法或同步代码块的前后插入
monitorenter
和 monitorexit
指令。4. Mark Word 和对象头
每个 Java 对象都有一个对象头,其中包含 Mark Word 和类型指针。Mark Word 中存储了对象的哈希码、GC 年龄、锁标志位等信息。当对象被锁定时,Mark Word 会保存锁的状态和持有锁的线程信息。
锁优化
为了提高程序的性能,Java 对锁进行了多种优化,包括偏向锁、轻量级锁和重量级锁。锁优化的目标是尽量减少线程同步带来的性能开销。
1. 偏向锁
偏向锁是 Java 6 引入的一种锁优化机制,旨在减少无竞争情况下的同步开销。当一个线程首次获取锁时,锁会偏向该线程,Mark Word 中会记录偏向线程的 ID。在之后的执行中,如果该线程再次请求锁,就不需要再进行 CAS 操作,从而减少了锁获取的开销。
偏向锁的获取过程:
- 首次获取锁时,将 Mark Word 的锁标志位设置为偏向模式,并记录偏向线程 ID。
- 之后相同线程获取锁时,直接通过检查 Mark Word 是否偏向当前线程来决定是否可以进入同步块。
偏向锁的撤销:
- 当另一个线程请求偏向锁时,锁会被撤销,并升级为轻量级锁。
偏向锁适用于无锁竞争的场景,但在高竞争场景下会带来额外的开销。在高竞争场景中,多个线程会同时争夺同一个锁资源,这种情况下,偏向锁容易被频繁撤销(撤销过程涉及到安全点暂停线程和锁膨胀),这会导致性能下降,因此,可以通过 JVM 参数
-XX:-UseBiasedLocking
来关闭偏向锁。2. 轻量级锁
轻量级锁也是 Java 6 引入的一种锁优化机制,旨在减少竞争不激烈情况下的同步开销。当一个线程尝试获取一个没有被锁定的锁时,会使用 CAS 操作将对象的 Mark Word 替换为指向栈中锁记录的指针。
轻量级锁的获取过程:
- 线程在进入同步块时,会首先检查对象的 Mark Word 是否处于无锁状态。
- 如果是,则使用 CAS 操作尝试将 Mark Word 替换为指向栈中锁记录的指针。
- 如果 CAS 操作成功,则表示获取了轻量级锁。
轻量级锁的释放:
- 线程在退出同步块时,会将 Mark Word 恢复为无锁状态,或者指向下一个锁记录。
轻量级锁的适用场景是锁竞争不激烈的情况,如果有多个线程竞争同一个锁,轻量级锁会膨胀为重量级锁。
3. 重量级锁
当偏向锁和轻量级锁都无法满足需求时,锁会膨胀为重量级锁。重量级锁使用操作系统的互斥量来实现,线程在获取锁失败后会被阻塞,直到锁被释放。
重量级锁的获取过程:
- 线程在进入同步块时,首先尝试使用 CAS 操作获取锁。
- 如果失败,则进入阻塞状态,并加入到锁的等待队列中。
重量级锁的释放:
- 线程在退出同步块时,会唤醒等待队列中的一个线程。
重量级锁适用于高竞争的场景,但由于涉及操作系统的线程调度,其性能开销较大。
锁优化的实践
在实际应用中,合理使用和优化锁可以显著提高程序的并发性能。以下是一些常见的锁优化实践。
1. 锁分解
锁分解是指将一个大锁分解为多个小锁,从而减少锁的竞争。例如,在一个有多个独立资源的类中,可以为每个资源分别使用一个锁,而不是为整个类使用一个锁。
2. 锁粗化
锁粗化是指将多个连续的锁操作合并为一个锁操作,从而减少锁的获取和释放次数。例如,在一个循环中多次获取和释放同一个锁,可以将锁操作移到循环外部。
可以优化为:
3. 读写锁
读写锁是一种特殊的锁,它允许多个读线程同时访问资源,但在写线程访问资源时,所有读线程和其他写线程都被阻塞。Java 提供了
ReentrantReadWriteLock
类来实现读写锁。4. 乐观锁
乐观锁是一种假设不会发生冲突的锁机制,通过版本号或时间戳来检测数据是否被修改。Java 中的
Atomic
类和 java.util.concurrent
包中的一些类使用了乐观锁机制。总结
synchronized
作为 Java 提供的一种内置锁机制,通过 Monitor 实现线程同步,保证了资源的互斥访问。为了提高性能,Java 引入了偏向锁、轻量级锁和重量级锁,并通过锁分解、锁粗化、读写锁和乐观锁等优化策略,进一步减少锁竞争带来的开销。在实际开发中,合理使用锁优化技术,可以显著提高程序的并发性能,降低锁竞争对系统资源的消耗。理解
synchronized
的实现原理和锁优化技术,对于编写高效的多线程程序具有重要意义。希望本文对你理解和应用 synchronized
及锁优化有所帮助。- 作者:奥利弗
- 链接:https://www.aolifu.org/article/synchronized
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。