Java synchronized 锁的 8 个经典问题小结

 更新时间:2026年02月12日 08:27:23   作者:Knight_AL  
本文通过8个实验探讨Java中synchronized关键字的不同锁机制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、前言

synchronized 的“锁的 8 个问题”几乎是并发面试必考。题目表面是在问“先打印短信还是邮件”,本质考察的是:对象锁 vs 类锁、同一对象 vs 不同对象、静态同步 vs 普通同步方法 等关键概念。下面用 8 个最典型的小实验,把底层锁定逻辑讲清楚。

二、代码基础模板

资源类 Phone:包含两个同步方法(锁 this)和一个普通方法(不加锁)。

class Phone {

    // 发送短信(同步方法:锁 this)
    public synchronized void sendSMS() {
        System.out.println(Thread.currentThread().getName() + " → 发送短信");
    }

    // 发送邮件(同步方法:锁 this)
    public synchronized void sendEmail() {
        System.out.println(Thread.currentThread().getName() + " → 发送邮件");
    }

    // 普通方法(无锁)
    public void getHello() {
        System.out.println(Thread.currentThread().getName() + " → hello");
    }
}

简单试跑:

public class Lock8Demo {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(phone::sendSMS, "AA").start();
        new Thread(phone::sendEmail, "BB").start();
    }
}

三、锁的 8 个问题(逐一验证)

下列 8 个小实验通过微调代码,验证不同锁语义。为更稳定观察先后顺序,部分用到小延迟。

(1)标准访问:先短信还是邮件?

class Phone {
    public synchronized void sendSMS() { System.out.println("发送短信"); }
    public synchronized void sendEmail() { System.out.println("发送邮件"); }
}

public class Lock8_1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(phone::sendSMS, "AA").start();
        new Thread(phone::sendEmail, "BB").start();
    }
}

现象:

发送短信
发送邮件

结论: 两个同步方法、同一对象 → 同一把对象锁(this),谁先拿锁谁先执行(通常按启动时机)。

(2)短信方法内停 4 秒:谁先?

class Phone {
    public synchronized void sendSMS() {
        try { Thread.sleep(4000); } catch (InterruptedException ignored) {}
        System.out.println("发送短信");
    }
    public synchronized void sendEmail() { System.out.println("发送邮件"); }
}

public class Lock8_2 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(phone::sendSMS, "AA").start();
        // 小延迟,确保 AA 先启动拿到锁
        try { Thread.sleep(100); } catch (InterruptedException ignored) {}
        new Thread(phone::sendEmail, "BB").start();
    }
}

现象:

发送短信
发送邮件

结论: 同一对象的两个同步方法互斥AA 持锁期间 BB 必须等待 → 串行。

(3)普通方法 + 同步方法:谁先?

class Phone {
    public synchronized void sendSMS() {
        try { Thread.sleep(4000); } catch (InterruptedException ignored) {}
        System.out.println("发送短信");
    }
    public void getHello() { System.out.println("hello"); }
}

public class Lock8_3 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(phone::sendSMS, "AA").start();
        try { Thread.sleep(100); } catch (InterruptedException ignored) {}
        new Thread(phone::getHello, "BB").start();
    }
}

现象:

hello
发送短信

结论: 普通方法不加锁,不受同步影响 → 直接执行,通常先于同步方法的输出。

(4)两部手机(两个对象),各自同步方法

public class Lock8_4 {
    public static void main(String[] args) {
        Phone p1 = new Phone();
        Phone p2 = new Phone();
        new Thread(p1::sendSMS, "AA").start();
        new Thread(p2::sendEmail, "BB").start();
    }
}

现象:

发送邮件
发送短信

结论: 每个实例都有独立的对象锁,不同对象互不干扰 → 可以并行。

(5)两个静态同步方法,一部手机

class Phone {
    public static synchronized void sendSMS() {
        try { Thread.sleep(4000); } catch (InterruptedException ignored) {}
        System.out.println("发送短信");
    }
    public static synchronized void sendEmail() { System.out.println("发送邮件"); }
}

public class Lock8_5 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(Phone::sendSMS, "AA").start();
        new Thread(Phone::sendEmail, "BB").start();
    }
}

现象:

发送短信
发送邮件

结论: 静态同步方法锁的是类对象 Phone.class(类锁),全类唯一 → 串行。

(6)两个静态同步方法,两部手机

public class Lock8_6 {
    public static void main(String[] args) {
        Phone p1 = new Phone();
        Phone p2 = new Phone();
        new Thread(Phone::sendSMS, "AA").start();
        new Thread(Phone::sendEmail, "BB").start();
    }
}

现象:

发送短信
发送邮件

结论: 仍是同一个 Phone.class 的锁,和多少实例无关 → 串行。

(7)一个静态同步 + 一个普通同步,一部手机

class Phone {
    public static synchronized void sendSMS() {
        try { Thread.sleep(4000); } catch (InterruptedException ignored) {}
        System.out.println("发送短信");
    }
    public synchronized void sendEmail() { System.out.println("发送邮件"); }
}

public class Lock8_7 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(Phone::sendSMS, "AA").start();
        try { Thread.sleep(100); } catch (InterruptedException ignored) {}
        new Thread(phone::sendEmail, "BB").start();
    }
}

现象:

发送邮件
发送短信

结论: 类锁(Phone.class)与对象锁(this)是两把不同的锁 → 可并行。

(8)一个静态同步 + 一个普通同步,两部手机

public class Lock8_8 {
    public static void main(String[] args) {
        Phone p1 = new Phone();
        Phone p2 = new Phone();
        new Thread(Phone::sendSMS, "AA").start();
        try { Thread.sleep(100); } catch (InterruptedException ignored) {}
        new Thread(p2::sendEmail, "BB").start();
    }
}

现象:

发送邮件
发送短信

结论: 类锁(Phone.class)与某个实例的对象锁(p2.this)互不影响 → 并行。

四、八大结论一览

编号场景描述锁对象是否同一锁执行特征
1同一对象两个同步方法对象锁(this)✅ 是串行
2同一对象,一个方法内睡眠对象锁(this)✅ 是串行
3同一对象,一个同步 + 一个普通方法对象锁 + 无锁❌ 否普通先
4两个对象,各自同步方法两个对象锁❌ 否并行
5一对象,两个静态同步方法类锁(Phone.class✅ 是串行
6两对象,两个静态同步方法类锁(Phone.class✅ 是串行
7一对象:一个静态同步 + 一个普通同步类锁 + 对象锁❌ 否并行
8两对象:一个静态同步 + 一个普通同步类锁 + 对象锁❌ 否并行

五、核心知识小结

  • 对象锁:synchronized 实例方法 / synchronized(this) → 锁的是当前实例。
  • 类锁:static synchronized / synchronized(Phone.class) → 锁的是类对象,全类唯一。
  • 普通方法:不参与加锁。
  • 代码块锁:synchronized(obj) 可自定义锁粒度,取决于 obj。

六、要点

  • synchronized 锁的是对象,不是方法。
  • 实例锁与类锁互不影响;不同实例的对象锁也互不影响。
  • 判断是否互斥的核心是:锁对象是否相同。

以上 8 个实验覆盖了 synchronized 的主流锁粒度考点。理解“锁定谁”这一件事,就能解释为什么有些代码会阻塞,有些能并行。
牢记:锁不是给代码加的,而是给对象加的。

到此这篇关于Java synchronized 锁的 8 个经典问题小结的文章就介绍到这了,更多相关Java synchronized 锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用spring boot 整合kafka,延迟启动消费者

    使用spring boot 整合kafka,延迟启动消费者

    这篇文章主要介绍了使用spring boot 整合kafka,延迟启动消费者的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • springboot集成druid,多数据源可视化,p6spy问题

    springboot集成druid,多数据源可视化,p6spy问题

    这篇文章主要介绍了springboot集成druid,多数据源可视化,p6spy问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • java List出现All elements are null问题及解决

    java List出现All elements are null问题及解决

    这篇文章主要介绍了java List出现All elements are null问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-11-11
  • Java中实现时间类型转换的代码详解

    Java中实现时间类型转换的代码详解

    这篇文章主要为大家详细介绍了Java中实现时间类型转换的相关方法,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考下
    2023-09-09
  • Java大文本并行计算实现过程解析

    Java大文本并行计算实现过程解析

    这篇文章主要介绍了Java大文本并行计算如何实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • Java聊天室之使用Socket实现传递图片

    Java聊天室之使用Socket实现传递图片

    这篇文章主要为大家详细介绍了Java简易聊天室之使用Socket实现传递图片功能,文中的示例代码讲解详细,具有一定的借鉴价值,需要的可以了解一下
    2022-10-10
  • Tomcat ClassLoader打破双亲委派源码解析

    Tomcat ClassLoader打破双亲委派源码解析

    这篇文章主要为大家介绍了Tomcat ClassLoader打破双亲委派源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • Java修饰符abstract与static及final的精华总结

    Java修饰符abstract与static及final的精华总结

    abstract、static、final三个修饰符是经常会使用的,对他们的概念必须非常清楚,弄混了会产生些完全可以避免的错误,比如final和abstract不能一同出现,static和abstract不能一同出现,下面我们来详细了解
    2022-04-04
  • spring中在xml配置中加载properties文件的步骤

    spring中在xml配置中加载properties文件的步骤

    这篇文章主要介绍了在spring中如何在xml配置中加载properties文件,本文分步骤给大家介绍在XML配置中加载properties文件的方法,需要的朋友可以参考下
    2023-07-07
  • 一文搞懂MyBatis一级缓存和二级缓存

    一文搞懂MyBatis一级缓存和二级缓存

    本文主要介绍了一文搞懂MyBatis一级缓存和二级缓存,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05

最新评论