Java synchronized 全面解析:从入门使用到底层原理(面试必备)

目录

一、入门:synchronized 是什么?解决什么问题?

1.1 核心定义

1.2 线程不安全的场景(为什么需要 synchronized)

1.3 加上 synchronized 后的效果

二、基础使用:synchronized 的三种用法(必掌握)

2.1 用法一:修饰实例方法(锁定当前对象 this)

2.2 用法二:修饰静态方法(锁定类对象 .class)

2.3 用法三:修饰代码块(锁定指定对象,灵活度最高)

案例1:锁定 this(等价于修饰实例方法,但粒度更细)

案例2:锁定 Class 对象(等价于修饰静态方法)

案例3:锁定自定义对象(常用于多线程共享资源的同步)

三种用法的核心区别(面试高频)

三、进阶:synchronized 的核心特性(理解这些,才算入门)

3.1 原子性(核心特性)

3.2 可见性(核心特性)

3.3 有序性(核心特性)

3.4 可重入性(重要特性)

3.5 非公平性(重要特性)

四、底层原理:synchronized 是如何实现的?(面试难点)

4.1 字节码层面:synchronized 的指令体现

4.2 JVM 层面:Monitor 锁与对象头

4.2.1 Java 对象头结构

4.2.2 Monitor 锁的本质

4.3 锁升级机制(JDK 1.8 优化,重点面试考点)

4.3.1 1. 无锁状态

4.3.2 2. 偏向锁(减少无竞争下的锁开销)

4.3.3 3. 轻量级锁(减少多线程竞争下的阻塞开销)

4.3.4 4. 重量级锁(最终锁形态)

锁升级总结(表格梳理)

六、面试高频问题

Q1:synchronized 的作用是什么?

Q2:synchronized 有几种用法?分别锁定什么对象?

Q3:synchronized 是公平锁还是非公平锁?

Q4:synchronized 是可重入锁吗?什么是可重入性?

Q5:synchronized 如何保证原子性、可见性、有序性?

Q6:JDK 1.6 对 synchronized 做了哪些优化?

Q7:synchronized 和 volatile 的区别?(高频中的高频)

Q8:synchronized 和 ReentrantLock 的区别?

七、总结


一、入门:synchronized 是什么?解决什么问题?

1.1 核心定义

synchronized 是 Java 中的内置锁(也叫监视器锁 Monitor Lock),是一种非公平、可重入的独占锁。它可以修饰方法、代码块,通过“锁定特定对象”的方式,让多个线程排队访问共享资源,从而保证同一时刻只有一个线程能执行被锁定的代码,解决线程安全问题。

1.2 线程不安全的场景(为什么需要 synchronized)

我们先看一个没有使用 synchronized 的例子,直观感受线程不安全的问题:模拟多线程执行“自增操作”(共享变量 count 从 0 自增到 10000)。

「预期结果」:10000 + 10000 = 20000 「实际结果」:大概率是小于20000的随机数(比如18937、19562)

「原因」:count++ 不是原子操作,当两个线程同时读取到同一个 count 值(比如100),线程1自增为101、线程2自增也为101,最终写入内存的是101,相当于“少加了一次”。这种多线程竞争共享资源导致的数据错乱,就是“线程不安全”,而 synchronized 就能解决这个问题。

1.3 加上 synchronized 后的效果

我们给 increment 方法加上 synchronized 修饰,再执行代码:

public class SynchronizedDemo { // 共享变量 private static int count = 0; // 自增方法(未加锁) public static void increment() { count++; // 看似一行代码,实际是 读取count → 自增 → 写入count 三步操作(非原子) } public static void main(String[] args) throws InterruptedException { // 线程1:执行10000次自增 Thread thread1 = new Thread(() -> { for (int i = 0; i < 10000; i++) { increment(); } }); // 线程2:执行10000次自增 Thread thread2 = new Thread(() -> { for (int i = 0; i < 10000; i++) { increment(); } }); thread1.start(); thread2.start(); thread1.join(); // 等待线程1执行完毕 thread2.join(); // 等待线程2执行完毕 System.out.println("最终count值:" + count); } }

「实际结果」:每次执行都是 20000,完全符合预期。

「原理」:synchronized 锁定了 increment 方法,让多个线程排队执行该方法,同一时刻只有一个线程能进入方法执行 count++,保证了自增操作的原子性,从而避免了数据错乱。

二、基础使用:synchronized 的三种用法(必掌握)

synchronized 有三种核心用法,分别是「修饰实例方法」、「修饰静态方法」、「修饰代码块」,每种用法锁定的“对象”不同,适用场景也不同,必须区分清楚(面试常考)。

2.1 用法一:修饰实例方法(锁定当前对象 this)

synchronized 修饰非静态方法(实例方法)时,锁定的是「调用该方法的对象实例」(this)。也就是说,同一个对象实例调用该方法时,会排队执行;不同对象实例调用该方法时,互不影响(因为锁定的是不同的对象)。

public class SynchronizedInstanceMethod { // 实例变量(属于对象实例的共享资源) private int num = 0; // synchronized 修饰实例方法,锁定 this(当前对象) public synchronized void add() { for (int i = 0; i < 1000; i++) { num++; } } public static void main(String[] args) throws InterruptedException { // 创建两个不同的对象实例 SynchronizedInstanceMethod obj1 = new SynchronizedInstanceMethod(); SynchronizedInstanceMethod obj2 = new SynchronizedInstanceMethod(); // 线程1:调用 obj1 的 add 方法 Thread t1 = new Thread(obj1::add); // 线程2:调用 obj2 的 add 方法 Thread t2 = new Thread(obj2::add); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("obj1.num:" + obj1.num); // 1000(预期) System.out.println("obj2.num:" + obj2.num); // 1000(预期) }

「说明」:t1 锁定的是 obj1,t2 锁定的是 obj2,两个线程锁定的是不同对象,因此可以同时执行 add 方法,互不干扰,最终两个对象的 num 都能达到 1000。

「注意」:如果两个线程调用的是同一个对象实例的 add 方法,就会排队执行,比如将 t2 改为 `new Thread(obj1::add)`,那么最终 obj1.num 会是 2000。

2.2 用法二:修饰静态方法(锁定类对象 .class)

synchronized 修饰静态方法时,锁定的不是 this,而是「当前类的 Class 对象」(每个类在 JVM 中只有一个 Class 对象,全局唯一)。因此,无论创建多少个对象实例,调用该静态方法时,所有线程都会排队执行(因为锁定的是同一个 Class 对象)。

public class SynchronizedStaticMethod { // 静态变量(属于类的共享资源,全局唯一) private static int num = 0; // synchronized 修饰静态方法,锁定 SynchronizedStaticMethod.class public static synchronized void add() { for (int i = 0; i < 1000; i++) { num++; } } public static void main(String[] args) throws InterruptedException { SynchronizedStaticMethod obj1 = new SynchronizedStaticMethod(); SynchronizedStaticMethod obj2 = new SynchronizedStaticMethod(); // 线程1:调用 obj1 的静态 add 方法 Thread t1 = new Thread(obj1::add); // 线程2:调用 obj2 的静态 add 方法 Thread t2 = new Thread(obj2::add); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("num:" + num); // 2000(预期) }

「说明」:虽然 t1 和 t2 调用的是不同对象实例的 add 方法,但 add 是静态方法,锁定的是 SynchronizedStaticMethod.class(全局唯一),因此两个线程会排队执行 add 方法,最终 num 累加为 2000。

2.3 用法三:修饰代码块(锁定指定对象,灵活度最高)

synchronized 修饰代码块时,需要手动指定「锁定的对象」(括号内填写锁定对象),语法:`synchronized(锁定对象) { 需同步的代码 }`。这种用法的灵活度最高,可以只锁定“需要同步的代码片段”(而非整个方法),减少锁的粒度,提升程序性能。

常见的锁定对象有两种:this(当前对象)、Class 对象,也可以是自定义的对象(只要保证多线程锁定的是同一个对象即可)。

案例1:锁定 this(等价于修饰实例方法,但粒度更细)
public class SynchronizedBlockThis { private int num = 0; public void add() { // 只锁定自增相关的代码块,而非整个 add 方法 synchronized (this) { for (int i = 0; i < 1000; i++) { num++; } } // 非同步代码(多个线程可同时执行) System.out.println("非同步代码执行"); } public static void main(String[] args) throws InterruptedException { SynchronizedBlockThis obj = new SynchronizedBlockThis(); Thread t1 = new Thread(obj::add); Thread t2 = new Thread(obj::add); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("num:" + obj.num); // 2000(预期) }

「说明」:只有 synchronized 代码块内的自增操作会被同步,代码块外的打印语句可以被多个线程同时执行,相比修饰整个实例方法,减少了锁的范围,提升了效率。

案例2:锁定 Class 对象(等价于修饰静态方法)
public class SynchronizedBlockClass { private static int num = 0; public void add() { // 锁定 Class 对象,全局唯一 synchronized (SynchronizedBlockClass.class) { for (int i = 0; i < 1000; i++) { num++; } } } public static void main(String[] args) throws InterruptedException { SynchronizedBlockClass obj1 = new SynchronizedBlockClass(); SynchronizedBlockClass obj2 = new SynchronizedBlockClass(); Thread t1 = new Thread(obj1::add); Thread t2 = new Thread(obj2::add); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("num:" + num); // 2000(预期) }
案例3:锁定自定义对象(常用于多线程共享资源的同步)
public class SynchronizedBlockCustom { private int num = 0; // 自定义锁定对象(必须保证多线程使用的是同一个对象) private final Object lock = new Object(); public void add() { // 锁定自定义对象 lock synchronized (lock) { for (int i = 0; i < 1000; i++) { num++; } } } public static void main(String[] args) throws InterruptedException { SynchronizedBlockCustom obj = new SynchronizedBlockCustom(); Thread t1 = new Thread(obj::add); Thread t2 = new Thread(obj::add); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("num:" + obj.num); // 2000(预期) }

「注意」:自定义锁定对象建议用 final 修饰,避免对象被修改(如果 lock 被重新赋值,会导致多个线程锁定不同对象,失去同步效果)。

三种用法的核心区别(面试高频)

用法

锁定对象

同步范围

适用场景

修饰实例方法

this(当前对象实例)

整个实例方法

实例变量的同步(多个线程操作同一个对象的实例变量)

修饰静态方法

当前类的 Class 对象

整个静态方法

静态变量的同步(多个线程操作同一个类的静态变量)

修饰代码块

手动指定(this、Class、自定义对象)

代码块内的代码

灵活控制同步粒度(只同步需要的代码,提升效率)

三、进阶:synchronized 的核心特性(理解这些,才算入门)

synchronized 能保证线程安全,核心依赖于它的三个特性:原子性、可见性、有序性,除此之外,它还是「可重入锁」、「非公平锁」,这些特性是面试的核心考点,必须逐一搞懂。

3.1 原子性(核心特性)

「原子性」:指一个操作或多个操作,要么全部执行完成,要么全部不执行,中间不会被其他线程打断。就像“银行转账”,从A账户扣钱、给B账户加钱,这两个操作必须同时完成,不能只执行一半。

synchronized 能保证「被锁定的代码块/方法」的原子性:同一时刻只有一个线程能进入被锁定的代码,避免了多线程同时执行导致的操作中断,从而保证了原子性。

「补充」:Java 中还有一些原子类(如 AtomicInteger),也能保证原子性,但底层是 CAS 机制,和 synchronized 的实现方式不同,后续会单独写博客拆解。

3.2 可见性(核心特性)

「可见性」:指当一个线程修改了共享变量的值后,其他线程能立即看到这个修改后的值。

为什么会有“不可见性”?因为每个线程都有自己的「工作内存」,线程读取共享变量时,会先从主内存中拷贝一份到工作内存,后续操作都基于工作内存中的副本;当线程修改了共享变量后,会先更新工作内存中的副本,再异步刷新到主内存中。如果其他线程此时读取共享变量,可能读取的还是主内存中未被更新的值,导致数据错乱。

synchronized 能保证可见性:当线程释放锁时,会将工作内存中修改后的共享变量值「强制刷新到主内存」;当线程获取锁时,会「清空工作内存中的共享变量副本」,重新从主内存中读取最新的值。这样就保证了多个线程看到的共享变量值是一致的。

3.3 有序性(核心特性)

「有序性」:指程序执行的顺序按照代码的先后顺序执行。但在 JVM 中,为了提升性能,会对代码进行「指令重排序」(在不影响单线程执行结果的前提下,调整指令的执行顺序)。

指令重排序在单线程环境下没问题,但在多线程环境下,可能会导致程序执行结果异常。例如:

// 共享变量 private static int a = 0; private static boolean flag = false; // 线程1执行 public static void thread1() { a = 1; // 指令1 flag = true; // 指令2 } // 线程2执行 public static void thread2() { if (flag) { // 指令3 System.out.println(a); // 可能输出 0,而非 1 } }

「原因」:JVM 可能会将线程1的指令1和指令2重排序(因为单线程下,先执行 flag=true 再执行 a=1,结果不变),如果线程1先执行 flag=true,线程2此时读取 flag 为 true,就会打印 a 的值(此时 a 还未被赋值为1,还是0),导致结果异常。

synchronized 能保证有序性:被 synchronized 锁定的代码块,会禁止指令重排序,保证代码按照编写顺序执行。同时,synchronized 还能保证“happens-before”关系(后续单独拆解),进一步保证多线程环境下的有序性。

3.4 可重入性(重要特性)

「可重入性」:指一个线程已经获取了某个锁,当它再次需要获取该锁时(比如递归调用被锁定的方法),可以直接获取,不会导致死锁。

synchronized 是可重入锁,我们用递归案例验证:

public class SynchronizedReentrant { // 锁定 this public synchronized void methodA() { System.out.println("进入 methodA"); methodB(); // 递归调用 methodB(同样锁定 this) } // 同样锁定 this public synchronized void methodB() { System.out.println("进入 methodB"); } public static void main(String[] args) { SynchronizedReentrant obj = new SynchronizedReentrant(); obj.methodA(); // 正常执行,不会死锁 } }

「执行结果」:

进入 methodA 进入 methodB

「说明」:线程调用 methodA 时,获取了 this 锁;在 methodA 中调用 methodB 时,因为 methodB 锁定的也是 this 锁,而线程已经持有该锁,所以可以直接进入 methodB,不会死锁。这就是可重入性的体现。

「补充」:synchronized 的可重入性,底层是通过「锁计数器」实现的:线程第一次获取锁时,计数器置为1;再次获取该锁时,计数器加1;释放锁时,计数器减1;当计数器为0时,锁才会被真正释放,其他线程才能获取。

3.5 非公平性(重要特性)

「公平锁」:多个线程等待同一个锁时,按照“先到先得”的顺序获取锁,不会出现线程饥饿(某个线程一直得不到锁)。

「非公平锁」:多个线程等待同一个锁时,线程获取锁的顺序是随机的,不一定是先到先得,可能存在某个线程多次获取锁,而其他线程长期等待的情况。

synchronized 是「非公平锁」:当锁被释放时,等待队列中的线程不会按照排队顺序获取锁,而是随机竞争,这样做的目的是「提升程序性能」(避免了线程排队的开销)。

「补充」:Java 中的 ReentrantLock 可以实现公平锁和非公平锁(默认非公平),后续会对比 synchronized 和 ReentrantLock 的区别。

四、底层原理:synchronized 是如何实现的?(面试难点)

前面我们讲了 synchronized 的用法和特性,接下来拆解最核心的底层原理 —— 它到底是靠什么实现“锁定对象、保证线程安全”的?

synchronized 的底层实现依赖于 JVM 的「监视器锁(Monitor)」,而 Monitor 的实现又和「对象头」、「字节码指令」密切相关。我们从“字节码层面”和“JVM 层面”两个维度拆解。

4.1 字节码层面:synchronized 的指令体现

我们先看一段简单的代码,反编译它的字节码,看看 synchronized 在字节码层面是如何表现的。

public class SynchronizedBytecode { private final Object lock = new Object(); public void test() { synchronized (lock) { System.out.println("synchronized 代码块"); } } }

使用 javac 编译后,再用 javap -v 反编译,查看 test 方法的字节码(核心部分):

public void test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: aload_0 1: getfield #2 // Field lock:Ljava/lang/Object; 4: dup 5: astore_1 6: monitorenter // 进入同步代码块,获取锁 7: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 10: ldc #4 // String synchronized 代码块 12: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 15: aload_1 16: monitorexit // 退出同步代码块,释放锁 17: goto 25 20: astore_2 21: aload_1 22: monitorexit // 异常情况下释放锁 23: aload_2 24: athrow 25: return

「核心指令解析」:

  1. monitorenter:当线程执行到 monitorenter 指令时,会尝试获取当前锁定对象的 Monitor 锁。
    1. 如果 Monitor 的计数器为 0(表示锁未被持有),则将计数器置为 1,当前线程成为 Monitor 的所有者;
    2. 如果当前线程已经是 Monitor 的所有者(可重入),则将计数器加 1;
    3. 如果 Monitor 被其他线程持有,则当前线程会阻塞,直到 Monitor 的计数器为 0,再尝试获取锁。
  2. monitorexit:当线程执行到 monitorexit 指令时,会释放 Monitor 锁,将 Monitor 的计数器减 1。当计数器减为 0 时,Monitor 会被释放,阻塞的线程可以尝试获取锁。

「注意」:字节码中会有两个 monitorexit 指令,第一个用于正常退出同步代码块时释放锁,第二个用于异常情况下释放锁(避免锁泄露)。

「补充」:如果是 synchronized 修饰方法(实例方法/静态方法),字节码层面不会出现 monitorenter 和 monitorexit 指令,而是会在方法的访问标志中增加 ACC_SYNCHRONIZED 标志,JVM 会根据该标志自动为方法添加 Monitor 的获取和释放逻辑(本质和代码块一致)。

4.2 JVM 层面:Monitor 锁与对象头

前面提到,synchronized 锁定的是“对象”,而 Monitor 锁是和“对象”绑定的 —— 每个 Java 对象在 JVM 中都有一个「对象头」,对象头中包含了「Monitor 锁的相关信息」,这也是 synchronized 能锁定对象的核心原因。

4.2.1 Java 对象头结构

Java 对象头由两部分组成(32位 JVM 为例):

  1. Mark Word(标记字,32位):存储对象的哈希码、分代年龄、锁状态标志、持有锁的线程 ID 等信息(核心部分);
  2. Klass Pointer(类型指针,32位):指向对象所属类的 Class 对象,JVM 通过该指针确定对象的类型。

其中,Mark Word 的结构会随着「锁状态」的变化而变化,synchronized 的锁升级机制也和 Mark Word 密切相关。Mark Word 的默认结构(无锁状态)如下:

位偏移

内容

说明

0-1位

锁状态标志

01:无锁状态;00:轻量级锁;10:重量级锁;11:偏向锁

2位

是否偏向锁

0:无偏向;1:偏向锁

3-23位

对象的哈希码

无锁/偏向锁状态下存储

24-30位

对象的分代年龄

GC 回收时使用

31位

无意义(固定为0)

-

4.2.2 Monitor 锁的本质

Monitor 是 JVM 内部的一个数据结构,也叫「监视器」,每个对象都有一个对应的 Monitor(可以理解为“锁的载体”)。Monitor 的结构如下(简化版):

  • Owner:存储当前持有 Monitor 的线程 ID,只有一个线程能成为 Owner;
  • EntryList:存储等待获取 Monitor 锁的线程队列(阻塞状态);
  • WaitSet:存储调用 wait() 方法后释放锁、进入等待状态的线程队列。

「Monitor 工作流程」:

  1. 线程执行 monitorenter 指令时,尝试获取 Monitor 的 Owner 权限:
    1. 如果 Owner 为空,当前线程成为 Owner,Monitor 计数器置为 1;
    2. 如果当前线程已是 Owner,计数器加 1(可重入);
    3. 如果 Owner 是其他线程,当前线程进入 EntryList 队列,阻塞等待。
  2. 线程执行 monitorexit 指令时,Monitor 计数器减 1:
    1. 如果计数器减为 0,释放 Owner 权限(置为 null),唤醒 EntryList 中一个阻塞的线程,让其尝试获取锁;
    2. 如果计数器不为 0(可重入场景),则当前线程仍持有 Owner 权限。

4.3 锁升级机制(JDK 1.8 优化,重点面试考点)

在 JDK 1.6 之前,synchronized 是「重量级锁」—— 每次获取和释放锁都需要调用操作系统的互斥量(Mutex),切换线程状态(用户态 ↔ 内核态),开销很大,效率很低。

为了优化 synchronized 的性能,JDK 1.6 及以后引入了「锁升级机制」:synchronized 的锁会从「无锁」→「偏向锁」→「轻量级锁」→「重量级锁」逐步升级,根据线程竞争的激烈程度动态调整,减少锁的开销。

锁升级的核心依据:「线程竞争的激烈程度」—— 竞争越激烈,锁的级别越高,开销也越大,但能保证线程安全。

4.3.1 1. 无锁状态

「场景」:没有线程竞争共享资源,对象处于无锁状态。

「Mark Word 特征」:锁状态标志为 01,是否偏向锁为 0,存储对象的哈希码和分代年龄。

4.3.2 2. 偏向锁(减少无竞争下的锁开销)

「场景」:只有一个线程多次获取同一个锁,没有其他线程竞争。

「核心思想」:既然只有一个线程获取锁,就不需要每次都执行 monitorenter/monitorexit 指令(避免内核态切换),而是将锁“偏向”于这个线程,后续该线程再次获取锁时,直接通过 Mark Word 验证即可,无需竞争。

「升级过程」:

  1. 线程第一次获取锁时,将 Mark Word 中的「是否偏向锁」设为 1,「锁状态标志」设为 11,同时存储当前线程的 ID;
  2. 该线程后续再次获取锁时,检查 Mark Word 中的线程 ID 是否为当前线程 ID:
    1. 如果是,直接获取锁,无需其他操作;
    2. 如果不是,说明有其他线程竞争,偏向锁升级为轻量级锁。

「补充」:偏向锁是 JDK 1.6 默认开启的,可通过 JVM 参数 -XX:-UseBiasedLocking 关闭。

4.3.3 3. 轻量级锁(减少多线程竞争下的阻塞开销)

「场景」:有多个线程竞争同一个锁,但竞争不激烈(线程交替获取锁,没有长时间阻塞)。

「核心思想」:当偏向锁被打破(有其他线程竞争),锁升级为轻量级锁,此时线程获取锁时,不再使用 Monitor(避免内核态切换),而是通过「CAS 机制」竞争锁。

「升级过程」:

  1. 线程获取锁时,JVM 会在当前线程的栈帧中创建一个「锁记录(Lock Record)」,存储锁定对象的 Mark Word 副本;
  2. 线程通过 CAS 操作,将锁定对象的 Mark Word 替换为「指向当前线程锁记录的指针」:
    1. 如果 CAS 成功,说明当前线程获取到锁,Mark Word 锁状态标志设为 00;
    2. 如果 CAS 失败,说明有其他线程正在竞争锁,此时会自旋(循环尝试 CAS)几次,如果自旋成功,仍能获取锁;如果自旋失败,说明竞争激烈,轻量级锁升级为重量级锁。

「补充」:自旋锁的目的是避免线程阻塞(内核态切换开销大),适合竞争不激烈的场景;如果竞争激烈,自旋会浪费 CPU 资源,此时升级为重量级锁更合适。

4.3.4 4. 重量级锁(最终锁形态)

「场景」:多个线程激烈竞争同一个锁,自旋多次仍无法获取锁。

「核心思想」:重量级锁依赖于 JVM 中的 Monitor 和操作系统的互斥量(Mutex),线程获取不到锁时,会进入阻塞状态(放弃 CPU 资源),直到锁被释放后被唤醒。

「Mark Word 特征」:锁状态标志为 10,存储指向 Monitor 的指针,此时线程获取锁会触发内核态切换,开销最大。

锁升级总结(表格梳理)

锁状态

适用场景

实现方式

开销

无锁

无线程竞争

无锁机制

偏向锁

单线程重复获取锁

Mark Word 存储线程 ID

极低

轻量级锁

多线程交替竞争,竞争不激烈

CAS 机制 + 自旋

较低

重量级锁

多线程激烈竞争

Monitor + 操作系统互斥量

较高

六、面试高频问题

结合前面的内容,整理了 synchronized 最常考的面试题,每个问题都给出简洁、精准的答案(适合面试时直接回答)。

Q1:synchronized 的作用是什么?

答:保证多线程环境下共享资源的原子性、可见性和有序性,避免数据错乱,实现线程安全。

Q2:synchronized 有几种用法?分别锁定什么对象?

答:三种用法:

  1. 修饰实例方法:锁定当前对象 this;
  2. 修饰静态方法:锁定当前类的 Class 对象;
  3. 修饰代码块:锁定括号内手动指定的对象(this、Class 对象、自定义对象)。

Q3:synchronized 是公平锁还是非公平锁?

答:非公平锁。当锁被释放时,等待队列中的线程不会按照排队顺序获取锁,而是随机竞争,目的是提升程序性能。

Q4:synchronized 是可重入锁吗?什么是可重入性?

答:是可重入锁。可重入性指一个线程已经获取了某个锁,当它再次需要获取该锁时(如递归调用),可以直接获取,不会导致死锁。底层通过锁计数器实现。

Q5:synchronized 如何保证原子性、可见性、有序性?

答:

  • 原子性:同一时刻只有一个线程能进入被锁定的代码,避免操作中断;
  • 可见性:释放锁时强制刷新工作内存到主内存,获取锁时清空工作内存,重新读取主内存;
  • 有序性:禁止指令重排序,保证代码按编写顺序执行。

Q6:JDK 1.6 对 synchronized 做了哪些优化?

答:引入了「锁升级机制」,从无锁 → 偏向锁 → 轻量级锁 → 重量级锁逐步升级,根据线程竞争激烈程度动态调整锁的级别,减少锁的开销;同时引入了自旋锁、偏向锁等优化,提升了 synchronized 的性能。

Q7:synchronized 和 volatile 的区别?(高频中的高频)

答:核心区别:volatile 只能保证可见性和有序性,不能保证原子性;synchronized 能保证原子性、可见性和有序性。

特性

synchronized

volatile

原子性

支持

不支持

可见性

支持

支持

有序性

支持

支持(禁止指令重排序)

锁机制

独占锁(可重入、非公平)

无锁机制

Q8:synchronized 和 ReentrantLock 的区别?

答:

  • 锁的实现:synchronized 是 JVM 内置锁,ReentrantLock 是 Java 代码实现的锁;
  • 公平性:synchronized 只能是非公平锁,ReentrantLock 可指定公平/非公平锁;
  • 灵活性:ReentrantLock 支持更多功能(如中断锁、超时锁、条件变量),synchronized 更简洁;
  • 性能:JDK 1.8 后两者性能差距不大,synchronized 更易维护,ReentrantLock 灵活性更高。

七、总结

synchronized 是 Java 并发编程的基础,也是面试必考的知识点,从入门到底层,核心要点可以总结为:

  1. 「用法」:三种用法(实例方法、静态方法、代码块),锁定对象不同,同步范围不同;
  2. 「特性」:原子性、可见性、有序性、可重入性、非公平性;
  3. 「底层」:依赖 JVM 的 Monitor 锁,通过 monitorenter/monitorexit 指令和对象头实现;
  4. 「优化」:JDK 1.8 锁升级机制(无锁→偏向锁→轻量级锁→重量级锁),减少开销;
  5. 「避坑」:锁定同一对象、避免可变对象、缩小锁粒度

Read more

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

摘要:本文聚焦OpenClaw从测试环境走向生产环境的核心痛点,围绕“性能优化、安全加固、监控运维”三大维度展开实操讲解。先明确生产环境硬件/系统选型标准,再通过硬件层资源管控、模型调度策略、缓存优化等手段提升响应速度(实测响应效率提升50%+);接着从网络、权限、数据三层构建安全防护体系,集成火山引擎安全方案拦截高危操作;最后落地TenacitOS可视化监控与Prometheus告警体系,配套完整故障排查清单和虚拟实战案例。全文所有配置、代码均经实测验证,兼顾新手入门实操性和进阶读者的生产级部署需求,帮助开发者真正实现OpenClaw从“能用”到“放心用”的跨越。 优质专栏欢迎订阅! 【DeepSeek深度应用】【Python高阶开发:AI自动化与数据工程实战】【YOLOv11工业级实战】 【机器视觉:C# + HALCON】【大模型微调实战:平民级微调技术全解】 【人工智能之深度学习】【AI 赋能:Python 人工智能应用实战】【数字孪生与仿真技术实战指南】 【AI工程化落地与YOLOv8/v9实战】【C#工业上位机高级应用:高并发通信+性能优化】 【Java生产级避坑指南:

By Ne0inhk
ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

🎬 渡水无言:个人主页渡水无言 ❄专栏传送门: 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》 ❄专栏传送门: 《freertos专栏》《STM32 HAL库专栏》 ⭐️流水不争先,争的是滔滔不绝  📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生 | 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生 在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连 目录 前言  一、实验基础说明 1.1、互斥体简介 1.2 本次实验设计思路 二、硬件原理分析(看过之前博客的可以忽略) 三、实验程序编写 3.1 互斥体 LED 驱动代码(mutex.c) 3.2.1、设备结构体定义(28-39

By Ne0inhk
Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 后端工程师扔给你一个 Swagger (OpenAPI) 文档地址,你会怎么做? 1. 对着文档,手写 Dart Model 类(容易写错字段类型)。 2. 手写 Retrofit/Dio 的 API 接口定义(容易拼错 URL)。 3. 当后端修改了字段名,你对着报错修半天。 这是重复劳动的地狱。 swagger_dart_code_generator 可以将 Swagger (JSON/YAML) 文件直接转换为高质量的 Dart 代码,包括: * Model 类:支持 json_serializable,带 fromJson/

By Ne0inhk
Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

文章目录 * 前言 * make/makefile * 文件的三个时间 * Linux第一个小程序-进度条 * 回车和换行 * 缓冲区 * 程序的代码展示 * git指令 * 关于gitee * Linux调试器-gdb使用 * 作业部分 前言 做 Linux 开发时,你是不是也遇到过这些 “卡脖子” 时刻?写 makefile 时,明明语法没错却报错,最后发现是依赖方法行没加 Tab;想提交代码到 gitee,记不清 git add/commit/push 的 “三板斧”,还得反复搜教程;用 gdb 调试程序,输了命令没反应,才想起编译时没加-g生成 debug 版本;甚至连写个进度条,都搞不懂\r和\n的区别,导致进度条乱跳…… 其实这些问题,

By Ne0inhk