资讯动态

Java synchronized核心原理与实战:从对象头到锁升级

发布时间:2026/9/15 1:42:02 来源:尧图企业网站定制
1. synchronized到底是什么先搞懂它解决什么问题我调试过一个很典型的线上问题库存扣减接口两个线程同时卖同一件商品库存余量明明只有1结果订单生成了两笔库存变成了-1。排查到最后罪魁祸首就是那个共享的库存变量没有做任何同步控制。这个问题在Java并发编程里太有代表性了也是synchronized存在的根本意义——让多线程访问共享资源时不再互相踩踏。synchronized是Java提供的内置锁也叫监视器锁Monitor Lock。它解决的核心问题是并发场景下的三个关键特性原子性、可见性和有序性。原子性很好理解就是一段代码要么全部执行完要么完全不执行不会执行到一半被其他线程插进来。可见性指的是一个线程修改了共享变量其他线程能立刻看到最新值而不是拿到一个缓存的旧值。有序性则是防止编译器或CPU为了优化把指令顺序打乱导致逻辑上不应该先执行的代码先执行了。很多人把synchronized简单理解成“加锁”但加锁只是一个手段它的真正价值是建立了一套内存屏障Memory Barrier机制。线程进入synchronized代码块时会清空工作内存中的共享变量副本直接从主内存重新读取退出时会把这个线程修改过的共享变量强制刷回主内存。这套机制配合JMMJava内存模型中的happens-before原则才保证了数据的正确性。学习synchronized最需要转变的一个观念是它锁住的不是代码而是对象。代码块只是被“包裹”在锁的保护范围内真正被锁的是某个对象实例。所以两个线程如果用的是不同的锁对象那synchronized根本起不到互斥效果。这个细节特别容易踩坑很多新手写同步代码时锁对象选错了从线上看并发问题依然频繁出现还百思不得其解。对于刚接触多线程的开发者我的建议是先从synchronized入手而不是直接上Lock、Atomic、ConcurrentHashMap这些高级并发工具。synchronized语法简单语义明确而且JDK 6之后性能已经被优化得非常好绝大多数业务场景完全够用。把它吃透了再去看Lock接口、AQSAbstractQueuedSynchronizer那些底层框架会轻松很多因为很多设计思路都是一脉相承的。2. 三种使用方式以及锁对象的选择2.1 同步实例方法锁的是this对象在方法声明上加synchronized关键字是最简单的用法。比如定义一个计数器类add方法用synchronized修饰那么同一个实例的多个线程调用add时会互斥执行。这个场景锁的对象是this也就是当前调用方法的实例。public class Counter { private int count 0; public synchronized void add() { count; } public synchronized int getCount() { return count; } }这里有个细节值得注意如果两个线程操作的是同一个Counter实例add互斥没问题但如果每个线程各自new了一个Counter那锁就完全失效了因为锁在不同的对象上。所以使用同步实例方法时必须确保多线程共享同一个实例。很多人在Spring里写Service默认是单例所以没问题但如果用了prototype作用域每个线程拿到的都是新实例那同步就形同虚设了。另一个容易被忽略的点是同步方法一旦声明整个方法体都被锁住。如果方法里有耗时操作比如远程调用、数据库查询、文件IO那锁的持有时间会被拉得很长其他线程全部阻塞等待性能会急剧下降。这种情况的优化方案是尽量缩小锁的范围把不需要同步的代码移出同步块。2.2 同步代码块锁对象自定义粒度更细同步代码块是很常见的做法它允许你指定任意对象作为锁也允许你只锁住真正需要保护的代码段。相比之下同步整个方法太过粗暴代码块可以精确控制锁的粒度和持锁时间。public class OrderService { private final Object lock new Object(); public void createOrder(Order order) { // 不需要同步的校验逻辑 validate(order); synchronized (lock) { // 只锁住库存扣减这段关键操作 int stock checkStock(order.getProductId()); if (stock order.getQuantity()) { deductStock(order.getProductId(), order.getQuantity()); saveOrder(order); } else { throw new RuntimeException(库存不足); } } // 后续的非关键操作 sendNotification(order); } }锁对象的选择有几个讲究。用专门的Object实例作为锁最安全也最推荐因为这个对象不会被外部访问到锁的控制权完全在我们自己手里。用this作为锁等价于同步实例方法如果外部代码也用了synchronized(this)那它们会互相阻塞形成了意想不到的锁竞争。用字符串常量做锁是最危险的做法因为JVM里字符串常量池会共享同一对象两个看似不同的业务模块如果用了相同内容的字符串做锁就会产生诡异的互斥。2.3 同步静态方法锁的是Class对象静态方法上加synchronized锁的是当前类的Class对象。注意这个锁和实例方法的锁是两把锁互不干扰。也就是说同一个类的静态同步方法和实例同步方法可以并行执行因为它们锁的不是同一个对象。public class ConfigManager { private static int configVersion 0; public static synchronized void updateConfig() { configVersion; } public static synchronized int getConfigVersion() { return configVersion; } }锁Class对象意味着对所有的实例都生效因为Class对象在JVM中是唯一的。所以静态同步方法天然适合保护静态变量或全局共享状态。使用的时候要注意一个经典误区如果实例同步方法内部访问了静态变量那它和静态同步方法之间并没有互斥关系这可能导致数据竞争需要额外加锁或改用统一的锁对象。还有一种组合用法静态方法和代码块结合用ClassName.class作为锁对象。这等价于锁了Class对象但它可以只锁一部分代码比静态同步整个方法更精细。2.4 锁对象选型速查表使用方式锁的对象作用范围适用场景同步实例方法this当前实例保护实例状态多线程共享同一实例同步代码块thisthis当前实例缩小同步范围保护实例某段逻辑同步代码块自定义对象指定对象该对象所有同步块推荐用法锁粒度可控避免外部干扰同步静态方法Class对象当前类所有实例保护静态变量、全局状态同步代码块Class.classClass对象当前类所有实例静态状态的部分代码加锁每次写synchronized之前先问自己一句这段代码要保护的数据是实例级别的还是类级别的这个对象是否会被外部代码直接引用并用来加锁想清楚了再动手能少踩很多坑。3. synchronized的底层原理从对象头到锁升级3.1 对象头与Mark WordJava对象在内存中的布局分为三部分对象头Header、实例数据Instance Data和对齐填充Padding。其中对象头里存了一个关键信息叫Mark Word它里面记录了对象的哈希码、GC分代年龄以及锁状态相关的位置信息。在64位JVM里Mark Word占8个字节64位。不同锁状态下这64个bit的含义是不同的。比如无锁状态下前56位存哈希码和分代年龄偏向锁状态下前54位存持有偏向锁的线程ID和epoch轻量级锁状态下指向栈中锁记录的指针重量级锁状态下指向Monitor对象的指针。这些位运算层面的细节不用背但需要理解一个核心结论锁的信息是存在对象头里的不同的锁状态用不同的bit组合表示。这也就解释了为什么synchronized能锁住任何对象——任何对象都有一个对象头对象头里都有Mark Word所以任何对象都能作为锁。锁升级说白了就是Mark Word里存的锁标志位在不断变化。3.2 Monitor机制与锁的获取释放流程synchronized的底层依赖操作系统的Monitor管程机制。每个Java对象都可以关联一个Monitor对象当多个线程同时访问synchronized代码块时JVM会通过Monitor来管理这些线程的进入、阻塞和退出。流程是这样的线程进入synchronized代码块时会被JVM的monitorenter指令处理尝试获取对象的Monitor所有权。如果Monitor的计数器为0说明当前没有线程持有锁该线程获得锁计数器加1。如果计数器不为0且持有者不是当前线程则该线程进入阻塞状态挂起到操作系统层。线程退出同步块时执行monitorexit指令计数器减1当计数器减到0时释放锁唤醒等待的线程。synchronized的Monitor机制天然支持可重入也就是说同一个线程可以多次进入同一个锁保护的代码块每次进入monitorenter会让计数器加1每次退出monitorexit会让计数器减1。这就是为什么在一个synchronized方法里调用另一个synchronized方法用的是同一把锁不会死锁的原因。可重入的实现让代码编写少了很多顾虑也避免了程序员自己维护锁状态的心智负担。3.3 JDK 6之后的锁升级路径JDK 6对synchronized做了一次里程碑式的优化引入了偏向锁和轻量级锁形成了现在的四层锁状态对应的升级方向是无锁 → 偏向锁 → 轻量级锁 → 重量级锁。锁只能升级不能降级这是JVM锁的一个重要特性。偏向锁是针对“只有一个线程访问同步块”的场景。第一个获取锁的线程会把线程ID记录到Mark Word里之后再次进入同步块时只需检查Mark Word里的线程ID是不是自己如果是就直接进入无需任何CAS操作。这个设计是针对“锁竞争并不存在”的场景省去了加锁本身的开销。一旦出现第二个线程来竞争偏向锁就撤销并升级为轻量级锁。轻量级锁通过CAS操作把Mark Word的锁记录指针指向当前线程的栈帧锁记录如果CAS成功表示获取锁成功如果失败说明有竞争锁会自旋一会儿再尝试自旋次数超过阈值后升级为重量级锁也就是内核态的Monitor加锁这时未获取锁的线程会进入阻塞涉及用户态与内核态的切换开销很大。理解锁升级的意义在于不要为了“快”而凭空想象去优化代码。很多人还停留在“synchronized性能差”的旧观念里实际上JDK 6之后在低竞争场景下synchronized的效率和ReentrantLock已经不相上下。真正要注意的是避免写高竞争的长临界区代码那才是性能瓶颈所在。3.4 锁消除与锁粗化除了锁升级JVM还有两个自动优化机制值得了解锁消除和锁粗化。锁消除发生在JIT编译阶段。如果JVM通过逃逸分析判定某个对象不会逃逸出当前线程那么对这个对象的所有锁操作都会被直接消除。比如方法内部new了一个局部对象对这个对象加锁由于它永远不会被其他线程访问到JVM会直接把锁去掉。所以不必为了“保险”给所有代码都加锁加对了锁才有意义。锁粗化则相反。JVM检测到同一线程反复对同一个对象加锁解锁会把这些操作合并成一个范围更大的锁减少加锁解锁的次数。比如循环里每次都synchronizedJVM可能把锁粗化到整个循环外面。这两个优化都是JVM自动执行的不需要也不允许程序员手动控制但理解它们有助于你在写代码时预判性能表现。4. 面试高频问题与底层机制synchronized的进阶考察点4.1 wait、notify与synchronized的配合synchronized和Object类的wait、notify方法的配合是面试中一个绕不开的考察点。wait方法必须在synchronized保护的代码块内调用否则会抛出IllegalMonitorStateException。原因是wait操作依赖Monitor对象的状态只有当前线程持有了锁才能把自身挂起并释放锁。一个标准的等待唤醒代码如下public class ProducerConsumer { private final Object lock new Object(); private ListInteger queue new ArrayList(); private int capacity 10; public void produce(int value) throws InterruptedException { synchronized (lock) { while (queue.size() capacity) { lock.wait(); } queue.add(value); lock.notifyAll(); } } public int consume() throws InterruptedException { synchronized (lock) { while (queue.isEmpty()) { lock.wait(); } int value queue.remove(0); lock.notifyAll(); return value; } } }这里有几个关键点。判断条件必须用while而不是if防止虚假唤醒spurious wakeup导致逻辑错误。虚假唤醒是wait方法可能在没有notify的情况下被意外唤醒如果用if判断唤醒后就直接往下走了没有重新检查条件就会出错。通知时用notifyAll而不是notify因为notify只随机唤醒一个线程被唤醒的线程可能不是你期望的那一个而且如果唤醒的是同类线程可能会导致死锁。notifyAll更稳妥。wait方法会释放锁这是一个重要的特性。线程调用wait后会从运行状态进入WAITING状态同时释放持有的Monitor锁让其他线程有机会进入同步代码块。而sleep方法虽然也让线程暂停但不会释放锁这两个方法在面试中经常被对比提问。4.2 synchronized与ReentrantLock的选择面试官几乎必问的一个问题是synchronized和ReentrantLock有什么区别什么场景该用哪个。这个问题考察的不仅是对API的熟悉程度更是对并发编程模型的理解深度。从功能上看ReentrantLock提供了几种synchronized没有的能力可中断的锁获取lockInterruptibly、超时获取锁tryLock(timeout)、公平锁、以及更灵活的条件变量Condition。这些能力在处理复杂的并发场景时非常有用。synchronized则不支持中断和超时一旦进入阻塞就只能等持锁线程释放。从性能上看JDK 6优化后的synchronized和ReentrantLock在大多数场景下没有显著差异。synchronized的锁升级机制已经覆盖了从无竞争到高竞争的多种场景而且它是JVM内置的不需要手动释放锁也不存在忘记释放导致死锁的问题。ReentrantLock的灵活性更高但需要手动lock和unlock通常建议配合try-finally使用写起来稍显繁琐。我的实际建议是能用synchronized就用synchronized即使JDK版本较老它也在不断优化。只有当你确实需要超时获取锁、可中断锁、公平锁这些高级功能时才考虑ReentrantLock。选择越简单的手段代码越容易维护。4.3 synchronized的可重入性与死锁的避免可重入性已经在前面提到过但面试时值得深入探讨。synchronized的可重入性是基于线程维度计算的而不是调用维度。同一个线程可以对同一个锁对象反复加锁每进入一次计数器加1。这个设计让代码在嵌套调用场景下非常省心。死锁是并发编程中最经典的问题也是面试的必考题。死锁产生的四个必要条件互斥、持有且等待、不可剥夺、循环等待。synchronized满足这四个条件所以使用不当就会死锁。一个典型的场景是两个线程分别持有锁A和锁B然后都尝试获取对方手里的锁互相等待谁也释放不了。避免死锁的常用手段包括调整锁的顺序让所有线程按相同顺序获取锁尽量缩小锁的范围减少持锁时间使用tryLock设置超时超时后自动放弃对象锁设计时尽量避免嵌套持有多个锁。这些经验在写实际业务时非常重要尤其在多资源并发操作的场景下。4.4 高并发下的性能优化技巧synchronized虽然是内置机制但使用不当依然会成为系统瓶颈。我总结几个实战中的优化思路。第一锁粒度要小。只锁真正需要保护的代码段把IO、网络调用、CPU密集型计算移出同步块。同步块内的时间越短锁竞争越少。第二锁分离。如果业务中有两类独立的共享资源不要用同一把锁保护它们。可以用两个不同的锁对象让两类操作能够并行执行。典型的例子是读写分离读操作不加锁或使用读写锁写操作单独加锁读取使用快照。第三减少临界区内的分支和循环。同步块内的代码逻辑越简单执行越快锁释放越早阻塞的线程越少。如果临界区内有复杂的业务逻辑尝试拆分成多个小临界区。第四避免在锁内做阻塞操作。比如在synchronized块里调用远程接口、等待网络响应、执行耗时数据库查询都会无限拉长持锁时间拖垮整个系统的吞吐量。5. 实际开发中常见的坑和排查思路5.1 锁对象选择错误的典型表现我在代码评审中经常看到这样一段代码public class UserService { private String lockKey userLock; public void updateUser(User user) { synchronized (lockKey) { // 更新逻辑 } } }这个lockKey是一个字符串常量但由于字符串常量池的存在JVM里所有内容相同的字符串字面量都指向同一个对象。如果另一个完全不相关的类也用了userLock作为锁它们的同步块就会互相阻塞造成莫名其妙的性能问题。更坏的情况是如果lockKey被定义为static final且内容恰好是别人的锁那影响范围可能扩大到整个JVM。正确做法是每个业务模块使用独立的私有Object作为锁或者在支持的情况下使用带有特定语义的锁对象。还有一点要注意如果一个业务逻辑同时被多个类使用锁对象应该放在共享的地方而不是各自持有一份否则就锁了个寂寞。5.2 锁粒度过大导致性能雪崩锁粒度过大是最常见的性能问题。比如整个方法加synchronized方法里又调用了第三方接口接口响应时间是500毫秒那所有调用该方法的线程都得排队等这500毫秒。如果同时有100个请求进来最后一个请求的等待时间会非常恐怖。最佳实践是缩小临界区只锁写入共享数据的代码。比如更新Redis缓存的场景只要锁住“读取旧的共享数据”和“写入新的共享数据”那一段而不是连参数校验、日志记录都一起锁住。实测下来一个库存系统从全方法加锁改成只锁核心扣减逻辑后吞吐量提升了近4倍而且代码逻辑没有变复杂。还有一种情况是把锁加在了循环内部。每循环一次就加一次锁频繁的锁竞争开销非常大。这时候应该把锁提到循环外面或者用并发集合类避免循环内同步。这是锁粗化的一个反面教训——JVM的锁粗化优化不是万能的减少不必要的手动锁操作才是正道。5.3 死锁的快速定位手段死锁的定位有两个常用工具。第一个是用jstack命令打印线程栈死锁发生时jstack输出中会明确显示“Found one Java-level deadlock”的字样并列出每个线程持有锁的情况和等待锁的情况定位到代码行号非常快。第二个是用JConsole或VisualVM这类图形化工具可以直观看到线程状态。死锁的线程通常会处于BLOCKED状态面板上会有“检测到死锁”的提示。生产环境不方便用图形工具的话jstack是首选一条命令就能拿到诊断信息。我自己排查死锁的经验是先看线程栈找到每个线程卡在哪一行代码然后看代码里锁的嵌套关系。绝大多数死锁都是两个或多个锁同时被多个线程反向获取导致的调整一下加锁顺序基本就能解决。5.4 synchronized与JMM可见性问题的关系synchronized不仅保证原子性还保证可见性和有序性。但很多开发者只知道它能互斥忽略了它同时具备的可见性保障。线程进入同步块时刷新工作内存退出时把修改写回主内存这就是为什么在synchronized保护下的共享变量读操作不需要使用volatile。一个常见的误区是认为加synchronized就万事大吉但其实如果读操作没有加锁只有写操作加了锁仍可能读到过期数据。必须保证对共享变量的所有读和写都使用同一个锁进行同步才能保证可见性和原子性。这也解释了为什么要在get方法上也加synchronized——只加在set方法上读线程看到的可能是旧值。在多线程访问共享数据时判断数据是否被正确同步简单粗暴的标准是所有读写路径是否都在同一把锁的保护下。只要有一条路径漏了锁整个同步的价值就会大打折扣。5.5 使用synchronized常见问题速查表问题原因解决方案加了synchronized还是出现并发错误锁对象不一致或锁的不是共享数据确认所有线程使用同一把锁且锁覆盖所有读写操作同步块内出现死锁多个锁嵌套获取顺序不一致统一锁顺序减少嵌套设置锁超时性能突然下降锁粒度过大临界区包含耗时操作缩小临界区把耗时操作移到锁外wait抛出IllegalMonitorStateExceptionwait未在synchronized块内调用先获取锁再调用wait锁对象是字符串莫名互斥字符串常量池导致不同代码共享同一把锁使用独立的Object实例作为锁存在多个实例导致锁失效锁是实例级别的不同实例锁不同改用静态锁对象或锁Class对象6. synchronized的常见面试题精讲6.1 经典八股synchronized锁的是什么面试官问“synchronized锁的是什么”不只要你答出锁的是对象还要你把三种用法对应的锁对象说清楚。实例方法锁this静态方法锁Class对象代码块锁指定对象。更进一步要能说出锁信息存放在对象头Mark Word里以及锁的升级路径。很多人在这一步就停住了但真正加分的答案是synchronized锁的本质是对象内部的Monitor监视器锁依赖底层操作系统的互斥原语实现但JDK 6后通过偏向锁和轻量级锁优化了锁获取和释放的开销。能把这个链条讲清楚面试官基本能确认你对并发底层有真正的理解。6.2 进阶八股synchronized和volatile的区别这道题考察的是对Java并发两个核心关键字的理解深度。volatile只保证可见性和有序性不保证原子性synchronized同时保证原子性、可见性和有序性。volatile本质上是内存屏障机制开销远小于synchronized但无法解决复合操作的原子性问题比如count。使用场景上像状态标志位、单例模式中的double-check、或者保证引用可见性这些场景适合用volatile。需要保证多个线程对共享数据整体操作的原子性时必须用synchronized或Lock。面试时能够举出适合自己的实际案例比如“我用volatile控制一个开关用synchronized保护库存扣减”会让回答更有说服力。6.3 硬核八股synchronized的锁升级全过程面试官很喜欢让候选人描述锁升级的完整过程。标准答案大致是无锁状态下线程A获取锁时JVM检查Mark Word发现是无锁且允许偏向于是通过CAS将线程A的ID写入Mark Word进入偏向锁状态此时线程B来竞争先尝试撤销偏向锁撤销成功则升级为轻量级锁线程A和线程B都通过CAS抢夺锁记录指针如果竞争进一步加剧CAS获取锁失败的线程会自旋自旋超过阈值或等待线程数过多则升级为重量级锁让线程进入操作系统内核态的阻塞队列。讲到这个程度基本能拿下这道题了。如果能补充一句“偏向锁在并发程度不高时可以显著降低无竞争场景下的锁开销但高竞争场景会有撤销偏向锁的成本”那就更显内行了。6.4 场景题多线程交替打印数字的多种实现这是一个很经典的手写题同时也是考验synchronized协作机制的好题。实现两个线程交替打印1到100一个线程打印奇数一个线程打印偶数。最纯粹的方式就是synchronized配合wait和notify。public class AlternatePrint { private int num 1; private final Object lock new Object(); public void printOdd() { synchronized (lock) { while (num 100) { if (num % 2 1) { System.out.println(Thread.currentThread().getName() : num); num; lock.notify(); } else { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } } public void printEven() { synchronized (lock) { while (num 100) { if (num % 2 0) { System.out.println(Thread.currentThread().getName() : num); num; lock.notify(); } else { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } } } }对比其他方案比如AtomicInteger自旋、Lock配合Condition、CompletableFuture等synchronized方案最简单直观也最贴近底层原理。这种题目不要求写出分布式级别的优雅方案重点是展示对线程协作和锁机制的理解。7. 从Thread到synchronized学习路径与实战建议7.1 建议的学习节奏很多人学Java多线程是跳跃式的今天看synchronized明天直接翻ConcurrentHashMap源码后天又去啃AQS。这种学法的结果是面试时每个概念都听过但一问细节就卡壳。我建议的路径是先老老实实学Thread的基础API理解线程的生命周期状态切换然后花大量时间吃透synchronized和volatile这两个核心同步工具再过渡到Lock体系、原子类、并发集合最后才是线程池和并发框架。synchronized是这条路径上最值得深挖的一环因为它把对象模型、锁、内存可见性、线程状态全都串了起来。把synchronized彻底搞懂后面的东西学起来会顺畅很多。7.2 与线程生命周期状态的联动理解synchronized和线程状态的关系非常紧密。线程进入同步块等待锁时线程状态是BLOCKED调用wait方法挂起时状态是WAITINGwait超时或join超时时是TIMED_WAITING。通过jstack看到线程状态为BLOCKED时基本能判断它在等一把锁结合线程栈信息就能定位是哪个锁。这个过程也可以反向验证如果你发现线程长期处于BLOCKED状态说明锁竞争激烈或持锁线程卡住了如果长期处于RUNNABLE但堆内存飙高可能涉及的是死循环或频繁GC这和锁没关系。能通过线程状态初步定位问题是实战中非常重要的技能。7.3 实战项目的练手建议学习synchronized最有效的不是背八股文而是动手写几个真实的场景题。我建议你尝试实现限流器、简单的生产者消费者模型、多线程批量处理任务时保证任务不重复执行、模拟抢票系统扣减库存。这些场景都能很好地锻炼锁的使用能力。你会发现真正理解synchronized之后写的线程安全代码会有一种“踏实”的感觉加锁的位置有依据锁对象选择有考究临界区范围有控制。而不是靠试错碰运气或者干脆每个方法都加synchronized图个省事。这种判断力恰恰是Java并发这块拉开普通开发者和高级开发者差距的关键。7.4 从synchronized到JUC的进阶之路学完synchronized下一步就是Java并发包java.util.concurrent。这里面的工具大多是为解决synchronized在某些场景下的不足而设计的。ReentrantLock提供了更灵活的锁控制Semaphore和CountDownLatch解决多线程协作ConcurrentHashMap用分段锁和CAS实现高并发容器ThreadPoolExecutor管理线程生命周期。但你会发现一个有意思的事实JUC里很多工具内部的并发控制仍然依赖synchronized。比如一些同步容器的局部方法、线程池的状态控制等。所以synchronized到底层实现始终是根基把根基打牢了再去理解JUC的抽象设计就会势如破竹。说句实在话我见过太多人把并发编程学成了API背诵能用却说不清原理出了问题又无法定位。synchronized这块内容虽然看起来只是一个小小的关键字但它牵涉到的对象头、Monitor、锁升级、内存模型这些底层知识恰恰是把“会用”提升为“懂原理”的分水岭。这也是我在实际编码和面试中得到的最大体会。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价