当前位置: 首页 > news >正文

JUC之synchronized关键字

文章目录

  • 一、synchronized核心摘要
    • 1.1 synchronized是什么?
    • 1.2 synchronized解决了什么问题?
    • 1.3 与volatile对比
  • 二、三种应用方式
    • 2.1 同步实例方法
    • 2.2 同步静态方法
    • 2.3 同步代码块
    • 2.4 锁的总结
      • 2.4.1 锁的类型
      • 2.4.2 关键结论
  • 三、深入原理: synchronized的底层实现
    • 3.1 对象结构与对象头(Object Header)
    • 3.2 Monitor 机制与工作流程
    • 3.3 锁升级优化:从偏向锁到重量级锁
      • 3.3.1 偏向锁 (Biased Locking)
      • 3.3.2 轻量级锁 (Lightweight Locking)
      • 3.3 重量级锁 (Heavyweight Locking)
    • 3.4 源码分析
      • 3.4.1 ObjectMonitor.java
      • 3.4.2 objectMonitor.hpp
      • 3.4.3 objectMonitor.cpp
      • 3.4.4 objectMonitor.cpp作用
    • 3.5 可重入性
    • 3.6 死锁
    • 3.7 synchronized执行流程【重要】
    • 3.8 字节码层面分析
  • 四、代码示例与场景分析
    • 4.1 原子性保证(计数器)
    • 4.2 错误的锁对象(经典陷阱)
    • 4.3 ❌ 错误用法示例
  • 五、总结与最佳实践

一、synchronized核心摘要

1.1 synchronized是什么?

synchronized 是 Java 内置的、最基本的互斥同步锁。它用于控制多个线程对共享资源的访问,确保同一时刻只有一个线程可以执行特定的代码段或方法。

1.2 synchronized解决了什么问题?

它解决了并发编程中的三大核心问题:

  1. 原子性(Atomicity): 确保被锁定的代码块作为一个不可分割的整体执行,不会被打断。
  2. 可见性(Visibility): 当一个线程释放锁时,它对共享变量所做的修改必须刷新到主内存。当另一个线程获取锁时,它将看到前一个线程所做的所有修改。
  3. 有序性(Ordering): 由于互斥性,被同步的代码段相当于一个单线程环境,可以防止指令重排序破坏代码语义(但仅限于同步块内部,同步块外部的代码仍可能被重排序)。

1.3 与volatile对比

特性synchronizedvolatile
互斥/原子性,可保证复合操作(如 i++)的原子性,只能保证单次读/写的原子性
可见性(通过 unlock 前的写回和 lock 后的重新加载)(强制读写主内存)
有序性(as-if-serial 语义在同步块内有效)(通过内存屏障禁止重排序)
使用成本重量级,涉及内核态与用户态切换(在优化后已大幅降低)轻量级,仅为 CPU 指令级别
阻塞,未获取锁的线程会进入阻塞状态,不会引起线程上下文切换

二、三种应用方式

2.1 同步实例方法

锁是当前对象实例(this)。

public class SynchronizedDemo {private int count = 0;// 锁是 this,当前对象实例public synchronized void increment() {count++; // 这个操作现在是原子的}// 等效的代码块写法public void incrementEquivalence() {synchronized (this) {count++;}}
}

使用场景: 当多个线程操作同一个对象实例的同步方法时,它们会相互竞争这把锁。

2.2 同步静态方法

锁是当前类的 Class 对象(例如 SynchronizedDemo.class)。

public class SynchronizedDemo {private static int staticCount = 0;// 锁是 SynchronizedDemo.classpublic static synchronized void staticIncrement() {staticCount++;}// 等效的代码块写法public static void staticIncrementEquivalence() {synchronized (SynchronizedDemo.class) {staticCount++;}}
}

使用场景: 当多个线程操作这个类的所有实例的静态同步方法时,它们会竞争同一把类锁。实例锁和类锁是两把不同的锁,互不干扰。

2.3 同步代码块

可以指定任意对象作为锁(lock,灵活性最高。

public class SynchronizedDemo {private final Object lock = new Object(); // 专门的锁对象private int count = 0;public void doSomething() {// ... 一些非线程安全的操作synchronized (lock) { // 使用一个专门的对象作为锁// 临界区代码count++;// ...}// ... 另一些非线程安全的操作}
}

优势: 减小了同步范围(锁粒度),提升了性能。使用私有的、final 的锁对象可以防止外部代码意外获得你的锁,从而提高安全性。

2.4 锁的总结

2.4.1 锁的类型

  1. 对象锁(实例锁)

    • 对象锁是针对实例对象的锁
    • 每个实例对象都有自己的对象锁
    • 不同实例对象的对象锁互不干扰
  2. 类锁(静态锁)

    • 类锁是针对类的锁,即 Class 对象的锁
    • 每个类只有一个类锁
    • 无论创建多少个实例,类锁都是同一个

2.4.2 关键结论

  • 对于普通同步方法,锁是当前实例对象(this)
  • 对于静态同步方法,锁是当前类的 Class 对象
  • 对于同步方法块,锁是 synchronized 括号里配置的对象
  • 同一时间,一个锁只能被一个线程持有
  • 对象锁和类锁互不干扰,可以同时存在

[!note]

  1. 一个对象当中如果有多个synchronized方法. 在某一个时间片内, 只要有一个线程调用了其中的某一个synchronized方法了, 那么其它线程只能等待;
    • 换句话说, 某一时刻内, 只能唯一一个线程去访问这些synchronized方法.
    • 重点记住: synchronized锁定的是当前对象「this」, 被锁定后,其它的线程都不能进入到当前对象的其它synchronized方法;
  2. 普通方法,不参与锁竞争, 所以和同步锁无关;
  3. 换成两个实例之后, 不是同一个对象了,那么也就不是同一把锁了;谁先谁后,并没有本质的联系了;
  4. 都换成静态同步方法. 锁定的是是类,而不是对象,即锁定的是「类名.class」, 即字节码对象也就是整个类「模板」;

三、深入原理: synchronized的底层实现

synchronized 的语义是通过 JVM 内部的 ObjectMonitor(监视器锁)机制来实现的,而每个 Java 对象都与一个 ObjectMonitor 相关联。这种关联信息存储在对象的对象头(Object Header) 中。

3.1 对象结构与对象头(Object Header)

一个对象在堆内存中的布局分为三部分:

  1. 对象头 (Header): 存储对象的元数据,如哈希码、GC 分代年龄、锁状态标志指向 Monitor 的指针等。
  2. 实例数据 (Instance Data): 存储对象的有效信息,即程序代码中定义的各种字段内容。
  3. 对齐填充 (Padding): 起占位符作用,确保对象大小是 8 字节的整数倍。

其中,对象头的 Mark Word 是理解锁的关键。它在 32 位和 64 位 JVM 中的长度不同(32bit / 64bit),其结构会随着锁标志位的变化而动态变化。

32 位 JVM 下 Mark Word 的存储结构:

32位 Mark Word
无锁状态 (01)
偏向锁状态 (01)
轻量级锁状态 (00)
重量级锁状态 (10)
GC标记 (11)
25bit: 对象哈希码
4bit: 分代年龄
1bit: 偏向模式(0)
2bit: 锁标志(01)
23bit: 线程ID
2bit: Epoch
4bit: 分代年龄
1bit: 偏向模式(1)
2bit: 锁标志(01)
30bit: 指向栈中锁记录的指针
2bit: 锁标志(00)
30bit: 指向重量级锁(Monitor)的指针
2bit: 锁标志(10)

2bit: 锁标志(11)

3.2 Monitor 机制与工作流程

每个 Java 对象都潜在地带有一个 Monitor(监视器)。可以把 Monitor 理解为一个特殊的房间,这个房间一次只能容纳一个线程。

这个房间有三部分组成

  • Entry Set(入口集): 想要进入房间的线程在此等候。
  • Owner(所有者): 当前正在房间内执行的线程。
  • Wait Set(等待集): 曾经进入过房间但主动暂时放弃资格的线程在此等候(通过 Object.wait())。

Monitor 是 JVM 提供的一种同步原语,每个 Java 对象都可以关联一个 Monitor。它包含:

  • Entry Set:等待获取锁的线程队列。
  • Wait Set:调用 wait() 后进入等待的线程队列。
  • Owner:当前持有锁的线程。
  • 计数器(recursions):记录重入次数

Java 对象在堆中存储时,其对象头(Object Header)包含:

  • Mark Word:存储哈希码、GC 分代年龄、锁状态等。
  • Klass Pointer:指向类元数据的指针。

Mark Word 结构(以 64 位 HotSpot 为例)

锁状态Mark Word 内容
无锁哈希码 + 分代年龄 + 01
偏向锁线程 ID + Epoch + 对象分代年龄 + 101
轻量级锁指向栈中锁记录的指针 + 00
重量级锁指向 Monitor 的指针 + 10
GC 标记空(用于 GC) + 11

synchronized 获取锁的流程:

Monitor结构
否/锁空闲
Entry Set
等待锁的线程
Owner
当前持有锁的线程
Wait Set
调用了wait的线程
线程尝试进入
synchronized块
锁是否被占用?
当前线程成为Owner
执行同步代码
线程进入Entry Set等待
进入阻塞(BLOCKED)状态
线程执行完同步代码
释放锁, Owner置为null
通知Entry Set中的线程
竞争锁

当线程执行到 synchronized 代码块时,JVM 会尝试让这个线程获取对象的 Monitor:

  1. 检查 Owner 是否为 null。
  2. 如果是 null,则将该线程设置为 Owner,进入临界区执行,计数器 +1。
  3. 如果不为 null,则说明有线程持有,当前线程进入 Entry Set 阻塞等待,计数器不变。
  4. 当 Owner 线程执行完毕,释放锁(Owner 置为 null,计数器 -1),并唤醒 Entry Set 中的线程来竞争锁。
  5. 被唤醒的线程和其他新来的线程一起竞争,竞争成功的成为新的 Owner,失败的继续阻塞。

wait(), notify(), notifyAll() 方法正是操作 Wait Set 中的线程。

3.3 锁升级优化:从偏向锁到重量级锁

在 JDK 1.6 之后,为了减少获得锁和释放锁带来的性能消耗,引入了偏向锁轻量级锁自旋锁等优化技术。锁的状态会根据竞争情况不断升级,这个过程是不可逆的。

锁的升级路径:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

flowchart TD
A[无锁] -- 第一个线程访问 --> B[偏向锁]
B -- 出现第二个线程竞争 --> C[轻量级锁<br>(自旋优化)]
C -- 自旋超过一定次数<br>或第三个线程来竞争 --> D[重量级锁]

3.3.1 偏向锁 (Biased Locking)

核心思想: 如果一个线程获得了锁,那么锁就进入偏向模式。当这个线程再次请求锁时,无需再做任何同步操作(如 CAS),直接即可获取锁。
目的: 消除数据在无竞争情况下的同步开销,提高单线程执行的性能。
升级: 一旦有另一个线程来尝试竞争这个锁,偏向模式立即宣告结束。如果持有偏向锁的线程还活着,则升级为轻量级锁;否则,锁被置为无锁状态,然后重新偏向新的线程。

⚠️ 注意:JDK 15+ 已默认禁用偏向锁,因在高并发场景下维护成本高。

线程A对象第一次获取锁设置偏向锁标志,记录线程A ID再次获取锁比对线程ID,直接获取锁(无CAS操作)线程A对象

3.3.2 轻量级锁 (Lightweight Locking)

核心思想: 当存在轻微的锁竞争时,线程不会立即阻塞,而是通过 CAS 操作自旋来尝试获取锁。
工作流程

  1. 在代码进入同步块时,JVM 会在当前线程的栈帧中创建一个名为锁记录(Lock Record) 的空间。
  2. 将对象头的 Mark Word 复制到锁记录中(称为 Displaced Mark Word)。
  3. 线程尝试用 CAS 将对象头的 Mark Word 替换为指向锁记录的指针。
    • 如果成功,当前线程获得锁,锁标志位变为 00
    • 如果失败,表示有其他线程竞争,当前线程会自旋(循环尝试)获取锁。
      升级: 如果自旋一定次数后(JDK 1.6 引入了自适应自旋)还未能获得锁,或者有第三个线程来竞争,锁就会膨胀为重量级锁。
线程A线程B对象持有偏向锁尝试获取锁,发现是偏向锁撤销偏向锁,升级为轻量级锁CAS尝试设置锁记录指针获取轻量级锁成功线程A线程B对象

3.3 重量级锁 (Heavyweight Locking)

核心思想: 也就是传统的 ObjectMonitor 机制。未抢到锁的线程会直接进入阻塞状态,等待被唤醒。
特点: 开销最大,涉及操作系统内核态的互斥量(mutex)操作,会导致线程上下文切换,但能应对高强度的锁竞争。

  • 触发:当自旋超过一定次数(默认 10 次,由 -XX:PreBlockSpin 控制)或等待线程较多时。
  • 实现:依赖操作系统互斥量(mutex),线程阻塞,进入内核态。
  • 开销大:涉及用户态/内核态切换、线程阻塞与唤醒。
线程A线程B线程C对象(重量级锁)获取锁成功获取锁失败,进入EntrySet阻塞获取锁失败,进入EntrySet阻塞释放锁唤醒线程B,获取锁线程A线程B线程C对象(重量级锁)

3.4 源码分析

[!tip]

  • ObjectMonitor.java
  • objectMonitor.hpp
  • objectMonitor.cpp
源码目录:
- \hotspot-8aac6d08b58e\agent\src\share\classes\sun\jvm\hotspot\runtime, ObjectMonitor.java
- \hotspot-8aac6d08b58e\src\share\vm\runtime, objectMonitor.cpp
- \hotspot-8aac6d08b58e\src\share\vm\runtime, objectMonitor.hpp
管程的概念: 管程就是Monitor对象.

3.4.1 ObjectMonitor.java

package sun.jvm.hotspot.runtime;import java.util.*;import sun.jvm.hotspot.debugger.*;
import sun.jvm.hotspot.oops.*;
import sun.jvm.hotspot.types.*;
import sun.jvm.hotspot.utilities.Observable;
import sun.jvm.hotspot.utilities.Observer;public class ObjectMonitor extends VMObject {static {VM.registerVMInitializedObserver(new Observer() {public void update(Observable o, Object data) {initialize(VM.getVM().getTypeDataBase());}});}private static synchronized void initialize(TypeDataBase db) throws WrongTypeException {heap = VM.getVM().getObjectHeap();Type type  = db.lookupType("ObjectMonitor");sun.jvm.hotspot.types.Field f = type.getField("_metadata");metadataFieldOffset = f.getOffset();f = type.getField("_object");objectFieldOffset = f.getOffset();f = type.getField("_owner");ownerFieldOffset = f.getOffset();f = type.getField("_stack_locker");stackLockerFieldOffset = f.getOffset();f = type.getField("_next_om");nextOMFieldOffset = f.getOffset();contentionsField  = new CIntField(type.getCIntegerField("_contentions"), 0);waitersField      = new CIntField(type.getCIntegerField("_waiters"), 0);recursionsField   = type.getCIntegerField("_recursions");ANONYMOUS_OWNER = db.lookupLongConstant("ObjectMonitor::ANONYMOUS_OWNER").longValue();}public ObjectMonitor(Address addr) {super(addr);}public Mark header() {return new Mark(addr.addOffsetTo(metadataFieldOffset));}// FIXME//  void      set_header(markWord hdr);// FIXME: must implement and delegate to platform-dependent implementation//  public boolean isBusy();public boolean isEntered(sun.jvm.hotspot.runtime.Thread current) {Address o = owner();if (current.threadObjectAddress().equals(o) ||current.isLockOwned(o)) {return true;}return false;}public boolean isOwnedAnonymous() {return addr.getAddressAt(ownerFieldOffset).asLongValue() == ANONYMOUS_OWNER;}public Address owner() { return addr.getAddressAt(ownerFieldOffset); }public Address stackLocker() { return addr.getAddressAt(stackLockerFieldOffset); }// FIXME//  void      set_owner(void* owner);public int    waiters() { return (int)waitersField.getValue(this); }public Address nextOM() { return addr.getAddressAt(nextOMFieldOffset); }// FIXME//  void      set_queue(void* owner);public long recursions() { return recursionsField.getValue(addr); }public OopHandle object() {Address objAddr = addr.getAddressAt(objectFieldOffset);if (objAddr == null) {return null;}return objAddr.getOopHandleAt(0);}public int contentions() {return (int)contentionsField.getValue(this);}// The following four either aren't expressed as typed fields in// vmStructs.cpp because they aren't strongly typed in the VM, or// would confuse the SA's type system.private static ObjectHeap    heap;private static long          metadataFieldOffset;private static long          objectFieldOffset;private static long          ownerFieldOffset;private static long          stackLockerFieldOffset;private static long          nextOMFieldOffset;private static CIntField     contentionsField;private static CIntField     waitersField;private static CIntegerField recursionsField;private static long          ANONYMOUS_OWNER;// FIXME: expose platform-dependent stuff
}

ObjectMonitor.java 是 Java 虚拟机中实现对象监视器(Monitor)的核心组件,主要用于支持 synchronized 关键字的同步机制。以下是其主要作用:

  1. 实现 synchronized 同步机制
  • ObjectMonitor 是 JVM 层面实现 synchronized 的核心数据结构
  • 它负责管理对象锁的获取、释放和等待队列
  • 提供了互斥访问和线程同步的基本功能
  1. 管理锁状态
  • 无锁状态:对象未被任何线程锁定
  • 偏向锁:偏向第一个获取锁的线程,减少同步开销
  • 轻量级锁:当不存在竞争时使用 CAS 操作获取锁
  • 重量级锁:当存在竞争时升级为重量级锁,使用 ObjectMonitor
  1. 维护等待队列
  • 管理阻塞等待锁的线程队列(Entry Set)
  • 管理调用 wait() 方法后进入等待状态的线程队列(Wait Set)
  • 实现线程的阻塞和唤醒机制

3.4.2 objectMonitor.hpp

src\share\vm\runtime\objectMonitor.cpp
src\share\vm\runtime\objectMonitor.hpp

在这里插入图片描述

// initialize the monitor, exception the semaphore, all other fields
// are simple integers or pointers
ObjectMonitor()
{_header = NULL;_count = 0;_waiters = 0,_recursions = 0;_object = NULL;_owner = NULL;_WaitSet = NULL;_WaitSetLock = 0;_Responsible = NULL;_succ = NULL;_cxq = NULL;FreeNext = NULL;_EntryList = NULL;_SpinFreq = 0;_SpinClock = 0;OwnerIsThread = 0;_previous_owner_tid = 0;
}bool try_enter(TRAPS);
void enter(TRAPS);
void exit(bool not_suspended, TRAPS);
void wait(jlong millis, bool interruptable, TRAPS);
void notify(TRAPS);
void notifyAll(TRAPS);

每一个对象创建的时候,都会带着一个对象监视器「管程」

每一个被锁住的对象都会和Monitor关联起来.

3.4.3 objectMonitor.cpp

try_enter

bool ObjectMonitor::try_enter(Thread *THREAD)
{if (THREAD != _owner){if (THREAD->is_lock_owned((address)_owner)){assert(_recursions == 0, "internal state error");_owner = THREAD;_recursions = 1;OwnerIsThread = 1;return true;}if (Atomic::cmpxchg_ptr(THREAD, &_owner, NULL) != NULL){return false;}return true;}else{_recursions++;return true;}
}

enter

void ATTR ObjectMonitor::enter(TRAPS)
{// The following code is ordered to check the most common cases first// and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.Thread *const Self = THREAD;void *cur;cur = Atomic::cmpxchg_ptr(Self, &_owner, NULL);if (cur == NULL){// Either ASSERT _recursions == 0 or explicitly set _recursions = 0.assert(_recursions == 0, "invariant");assert(_owner == Self, "invariant");// CONSIDER: set or assert OwnerIsThread == 1return;}if (cur == Self){// TODO-FIXME: check for integer overflow!  BUGID 6557169._recursions++;return;}if (Self->is_lock_owned((address)cur)){assert(_recursions == 0, "internal state error");_recursions = 1;// Commute owner from a thread-specific on-stack BasicLockObject address to// a full-fledged "Thread *"._owner = Self;OwnerIsThread = 1;return;}// We've encountered genuine contention.assert(Self->_Stalled == 0, "invariant");Self->_Stalled = intptr_t(this);// Try one round of spinning *before* enqueueing Self// and before going through the awkward and expensive state// transitions.  The following spin is strictly optional ...// Note that if we acquire the monitor from an initial spin// we forgo posting JVMTI events and firing DTRACE probes.if (Knob_SpinEarly && TrySpin(Self) > 0){assert(_owner == Self, "invariant");assert(_recursions == 0, "invariant");assert(((oop)(object()))->mark() == markOopDesc::encode(this), "invariant");Self->_Stalled = 0;return;}assert(_owner != Self, "invariant");assert(_succ != Self, "invariant");assert(Self->is_Java_thread(), "invariant");JavaThread *jt = (JavaThread *)Self;assert(!SafepointSynchronize::is_at_safepoint(), "invariant");assert(jt->thread_state() != _thread_blocked, "invariant");assert(this->object() != NULL, "invariant");assert(_count >= 0, "invariant");// Prevent deflation at STW-time.  See deflate_idle_monitors() and is_busy().// Ensure the object-monitor relationship remains stable while there's contention.Atomic::inc_ptr(&_count);JFR_ONLY(JfrConditionalFlushWithStacktrace<EventJavaMonitorEnter> flush(jt);)EventJavaMonitorEnter event;if (event.should_commit()){event.set_monitorClass(((oop)this->object())->klass());event.set_address((uintptr_t)(this->object_addr()));}{ // Change java thread status to indicate blocked on monitor enter.JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);Self->set_current_pending_monitor(this);DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);if (JvmtiExport::should_post_monitor_contended_enter()){JvmtiExport::post_monitor_contended_enter(jt, this);// The current thread does not yet own the monitor and does not// yet appear on any queues that would get it made the successor.// This means that the JVMTI_EVENT_MONITOR_CONTENDED_ENTER event// handler cannot accidentally consume an unpark() meant for the// ParkEvent associated with this ObjectMonitor.}OSThreadContendState osts(Self->osthread());ThreadBlockInVM tbivm(jt);// TODO-FIXME: change the following for(;;) loop to straight-line code.for (;;){jt->set_suspend_equivalent();// cleared by handle_special_suspend_equivalent_condition()// or java_suspend_self()EnterI(THREAD);if (!ExitSuspendEquivalent(jt))break;//// We have acquired the contended monitor, but while we were// waiting another thread suspended us. We don't want to enter// the monitor while suspended because that would surprise the// thread that suspended us.//_recursions = 0;_succ = NULL;exit(false, Self);jt->java_suspend_self();}Self->set_current_pending_monitor(NULL);// We cleared the pending monitor info since we've just gotten past// the enter-check-for-suspend dance and we now own the monitor free// and clear, i.e., it is no longer pending. The ThreadBlockInVM// destructor can go to a safepoint at the end of this block. If we// do a thread dump during that safepoint, then this thread will show// as having "-locked" the monitor, but the OS and java.lang.Thread// states will still report that the thread is blocked trying to// acquire it.}Atomic::dec_ptr(&_count);assert(_count >= 0, "invariant");Self->_Stalled = 0;// Must either set _recursions = 0 or ASSERT _recursions == 0.assert(_recursions == 0, "invariant");assert(_owner == Self, "invariant");assert(_succ != Self, "invariant");assert(((oop)(object()))->mark() == markOopDesc::encode(this), "invariant");// The thread -- now the owner -- is back in vm mode.// Report the glorious news via TI,DTrace and jvmstat.// The probe effect is non-trivial.  All the reportage occurs// while we hold the monitor, increasing the length of the critical// section.  Amdahl's parallel speedup law comes vividly into play.//// Another option might be to aggregate the events (thread local or// per-monitor aggregation) and defer reporting until a more opportune// time -- such as next time some thread encounters contention but has// yet to acquire the lock.  While spinning that thread could// spinning we could increment JVMStat counters, etc.DTRACE_MONITOR_PROBE(contended__entered, this, object(), jt);if (JvmtiExport::should_post_monitor_contended_entered()){JvmtiExport::post_monitor_contended_entered(jt, this);// The current thread already owns the monitor and is not going to// call park() for the remainder of the monitor enter protocol. So// it doesn't matter if the JVMTI_EVENT_MONITOR_CONTENDED_ENTERED// event handler consumed an unpark() issued by the thread that// just exited the monitor.}if (event.should_commit()){event.set_previousOwner((uintptr_t)_previous_owner_tid);event.commit();}if (ObjectMonitor::_sync_ContendedLockAttempts != NULL){ObjectMonitor::_sync_ContendedLockAttempts->inc();}
}

try_lock()

int ObjectMonitor::TryLock(Thread *Self)
{for (;;){void *own = _owner;if (own != NULL)return 0;if (Atomic::cmpxchg_ptr(Self, &_owner, NULL) == NULL){// Either guarantee _recursions == 0 or set _recursions = 0.assert(_recursions == 0, "invariant");assert(_owner == Self, "invariant");// CONSIDER: set or assert that OwnerIsThread == 1return 1;}// The lock had been free momentarily, but we lost the race to the lock.// Interference -- the CAS failed.// We can either return -1 or retry.// Retry doesn't make as much sense because the lock was just acquired.if (true)return -1;}
}

3.4.4 objectMonitor.cpp作用

objectMonitor.cpp 文件实现了Java对象监视器(Object Monitor)的核心功能:

  1. 同步机制实现:提供Java synchronized关键字的底层实现
  2. 线程排队管理:管理等待获取锁的线程队列(EntryList和CXQ)
  3. 等待/通知机制:实现Object.wait()和Object.notify()等方法
  4. 内存可见性保障:通过内存屏障确保多线程环境下的数据一致性
  5. 平台适配:针对不同操作系统的特定优化实现

3.5 可重入性

同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动释放锁「锁的对象得是同一个对象」, 不会因为之前已经获取到锁而没有释放进而发生阻塞.

例如: 有一个synchronized修饰的的递归方法,程序第2次进入的时候,会自动获取该方法的锁,而不会阻塞住.

ReentrantLock和synchronized都是可重入锁,可重入锁在一定程度上可以避免死锁.

synchronized 是可重入锁,同一线程可以多次获取同一把锁,不会导致死锁。

可重入锁的分类

  • 隐式锁: 「synchronized关键字使用的锁」, 默认是可重入锁
    • 同步代码块
    • 同步方法
    • 简单来说,指的是可重复递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不会发生死锁,这样的锁称为可重入锁,即在一个synchronized修饰的方法或者是代码块的内部调用本类的其它synchronized修饰的方法或者代码块时,是永远可以得到锁的.
  • 显示锁
    • Lock锁.例如ReentrantLock

示例代码:

// 同步代码块, 可重入锁
public class T0 {public static void main(String[] args) {final Object lock = new Object();new Thread(() ->{// 同步代码块可重入锁synchronized (lock){System.out.println("同步代码块一");synchronized (lock){System.out.println("同步代码块二");synchronized (lock){System.out.println("同步代码块三");}}}}, "线程A: ").start();}
}// 同步方法
public synchronized void m1(){System.out.println("m1 method");m2();
}public synchronized void m2(){System.out.println("m2 method");
}public static void main(String[] args) {new T0().m1();
}

可重入原理Monitor 中有一个计数器(count),线程获取锁时计数器加 1,释放锁时计数器减 1,当计数器为 0 时才真正释放锁。

3.6 死锁

package cn.tcmeta.demo04;import java.util.concurrent.TimeUnit;public class T1 {public static void main(String[] args) {final Object lock1 = new Object();final Object lock2 = new Object();new Thread(() ->{synchronized (lock1){System.out.println(Thread.currentThread().getName() + " ----- " +  "持有锁对象lock1");try {TimeUnit.MILLISECONDS.sleep(2000);}catch (Exception e){e.printStackTrace();}synchronized (lock2){System.out.println(Thread.currentThread().getName() + " ----- " +  "拿到了锁对象lock2");}}}, "线程A: ").start();new Thread(() ->{synchronized (lock2){System.out.println(Thread.currentThread().getName() + " ----- " +  "持有锁对象lock2");try {TimeUnit.MILLISECONDS.sleep(2000);}catch (Exception e){e.printStackTrace();}synchronized (lock1){System.out.println(Thread.currentThread().getName() + " ----- " +  "拿到了锁对象lock1");}}}, "线程B: ").start();}
}

排查死锁问题:

  • jps, 查看当前运行的java进程
  • jstack 进程id, 查看当前java的堆栈信息

3.7 synchronized执行流程【重要】

在这里插入图片描述

3.8 字节码层面分析

我们来看一个简单的 synchronized 方法的字节码:

public class SyncDemo {public synchronized void syncMethod() {System.out.println("Hello");}
}

使用 javap -c SyncDemo 查看字节码:

public synchronized void syncMethod();descriptor: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZED   // 注意这个标志Code:0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #3                  // String Hello5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: return

🔍 关键点:

  • 方法被标记为 ACC_SYNCHRONIZED,JVM 在调用时自动尝试获取对象锁。
  • 方法执行完毕或抛出异常时,自动释放锁。

而对于同步代码块:

synchronized (this) {System.out.println("Hello");
}

字节码会生成 monitorentermonitorexit 指令

 0: aload_01: monitorenter          // 获取锁2: getstatic #25: ldc #37: invokevirtual #4
10: monitorexit           // 释放锁
11: goto 19
14: astore_1
15: monitorexit           // 异常路径也必须释放锁
16: aload_1
17: athrow

monitorexit 必须成对出现(正常路径 + 异常路径),由编译器自动插入,确保锁一定被释放。

四、代码示例与场景分析

4.1 原子性保证(计数器)

public class SafeCounter {private int count = 0;private final Object lock = new Object(); // 显式锁对象// 方式1: 同步方法public synchronized void incrementByMethod() {count++;}// 方式2: 同步代码块(更灵活,粒度更细)public void incrementByBlock() {// ... 一些不需要同步的准备工作synchronized (lock) {count++;}// ... 一些不需要同步的收尾工作}public int getCount() {return count;}public static void main(String[] args) throws InterruptedException {SafeCounter counter = new SafeCounter();Thread t1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {counter.incrementByBlock();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {counter.incrementByBlock();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + counter.getCount()); // 总是 20000}
}

4.2 错误的锁对象(经典陷阱)

public class WrongLockDemo {private static final int NUM_THREADS = 100;private static int sharedCount = 0;// 锁对象是 Integer,但注意 Integer 有缓存池!private static final Integer LOCK = 0; public static void main(String[] args) throws InterruptedException {Thread[] threads = new Thread[NUM_THREADS];for (int i = 0; i < NUM_THREADS; i++) {threads[i] = new Thread(() -> {// 严重问题: Integer 是 immutable 的,每次 ++ 操作都会创建一个新对象。// 不同线程可能锁的是不同的对象实例,导致同步完全失效!synchronized (Integer.valueOf(LOCK + (int) (Math.random() * 10))) { for (int j = 0; j < 100; j++) {sharedCount++;}}});threads[i].start();}for (Thread t : threads) {t.join();}// 结果大概率远小于 100 * 100 * 100System.out.println("Result with bad lock: " + sharedCount); }
}

正确做法: 锁对象应该是 private final 的,并且是一个不可变的、不会被意外复用的对象(如 private final Object lock = new Object();)。

4.3 ❌ 错误用法示例

public class BadSync {public synchronized void methodA() { /* ... */ }public synchronized void methodB() { /* ... */ }
}

两个方法共用 this 锁,即使逻辑无关也会相互阻塞。

✅ 正确做法:细粒度锁

public class GoodSync {private final Object lockA = new Object();private final Object lockB = new Object();public void methodA() {synchronized (lockA) { /* ... */ }}public void methodB() {synchronized (lockB) { /* ... */ }}
}

五、总结与最佳实践

  1. 理解锁的范围: 同步代码块的粒度越小,性能越好。只同步真正需要同步的临界区。
  2. 谨慎选择锁对象
    • 对于实例方法,锁是 this,要确保所有竞争线程操作的是同一个实例
    • 对于静态方法,锁是 Class 对象。
    • 最推荐的方式是使用一个私有的、final 的普通 Object 对象作为显式锁。
  3. 避免死锁: 确保多个线程以一致的顺序获取多个锁。例如,线程 A 先锁 X 再锁 Y,那么线程 B 也应该是先锁 X 再锁 Y,而不是先锁 Y 再锁 X。
  4. 考虑性能: 在低竞争场景下,synchronized 经过锁升级优化后性能已经非常优秀。在高竞争场景下,可以考虑 java.util.concurrent.locks.ReentrantLock,它提供了更灵活的机制(如可中断、超时、公平锁等),但需要手动释放锁。
  5. 优先使用并发容器: 在很多场景下,使用 ConcurrentHashMapCopyOnWriteArrayList 等并发容器比你自己用 synchronized 同步普通容器更高效、更安全。

synchronized 是 Java 并发世界的基石,深入理解其原理和最佳实践,是编写正确、高效多线程程序的关键。

  • 在日常开发中,优先使用 synchronized 解决线程安全问题;
  • 只有在需要可中断、超时、公平性等高级特性时,才考虑 ReentrantLock
http://www.dtcms.com/a/352569.html

相关文章:

  • Dify 从入门到精通(第 57/100 篇):Dify 的知识库扩展(进阶篇)
  • 8.26学习总结
  • 在 C# 中使用 Consul 客户端库实现服务发现
  • 卷积操作现实中的意义
  • 发力低空经济领域,移动云为前沿产业加速崛起注入云端动能
  • 微服务-24.网关登录校验-实现登录校验
  • Linux系统日志分析与存储
  • 机器学习:前篇
  • 从行业智能体到一站式开发平台,移动云推动AI智能体规模化落地
  • 产品经理操作手册(3)——产品需求文档
  • Duplicate Same Files Searcher v10.7.0,秒扫全盘重复档,符号链接一键瘦身
  • 【软件测试面试】全网最全,自动化测试面试题总结大全(付答案)
  • 告别出差!蓝蜂物联网网关让PLC程序远程修改零延迟
  • 二、JVM 入门 —— (四)堆以及 GC
  • 渗透测试术语大全(超详细)
  • C++ STL 顶层设计与安全:迭代器、失效与线程安全
  • 【C++游记】栈vs队列vs优先级队列
  • 算法编程实例-快乐学习
  • 随机森林实战:在鸢尾花数据集上与决策树和逻辑斯蒂回归进行对比
  • AI安全监控与人才需求的时间悖论(对AI安全模型、AI安全人才需求的一些思考)
  • AIDL和HIDL的AudioHal对比
  • Maya绑定基础: FK 和 IK 介绍和使用
  • lottie动画动态更改切图添加事件
  • 五自由度磁悬浮轴承:精准狙击转子质量不平衡引发的同频振动
  • pycharm 远程连接服务器报错
  • NeRAF、ImVid论文解读
  • 2007-2022年上市公司企业关联交易数据
  • 面向对象爬虫架构设计:构建高复用、抗封禁的爬虫系统​
  • 工业数据消费迎来“抖音式”革命:TDengine IDMP 让数据自己开口说话
  • 利用 Java 爬虫按关键字搜索 1688 商品详情 API 返回值说明实战指南