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 锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java布尔值Boolean和boolean之间转换实例用法

    Java布尔值Boolean和boolean之间转换实例用法

    在本篇文章里小编给大家整理的是一篇关于Java布尔值Boolean和boolean之间转换实例用法内容,有需要的朋友们跟着学习参考下。
    2021-06-06
  • 关于Javaweb的转发和重定向详解

    关于Javaweb的转发和重定向详解

    这篇文章主要介绍了关于Javaweb的转发和重定向详解,请求的转发,是指服务器收到请求后,从一个服务器端资源跳转到同一个服务器端另外一个资源的操作,需要的朋友可以参考下
    2023-05-05
  • SpringBoot自动装配之Condition深入讲解

    SpringBoot自动装配之Condition深入讲解

    @Conditional表示仅当所有指定条件都匹配时,组件才有资格注册。该@Conditional注释可以在以下任一方式使用:作为任何@Bean方法的方法级注释、作为任何类的直接或间接注释的类型级别注释@Component,包括@Configuration类、作为元注释,目的是组成自定义构造型注释
    2023-01-01
  • Hadoop多Job并行处理的实例详解

    Hadoop多Job并行处理的实例详解

    这篇文章主要介绍了Hadoop多Job并行处理的实例详解的相关资料,希望通过本文能帮助到大家,需要的朋友可以参考下
    2017-10-10
  • Java JDBC导致的反序列化攻击原理解析

    Java JDBC导致的反序列化攻击原理解析

    这篇文章主要介绍了Java JDBC导致的反序列化攻击原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • Java验证码功能的实现方法

    Java验证码功能的实现方法

    这篇文章主要为大家详细介绍了Java验证码功能的实现方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • 基于自定义BufferedReader中的read和readLine方法

    基于自定义BufferedReader中的read和readLine方法

    下面小编就为大家分享一篇基于自定义BufferedReader中的read和readLine方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-12-12
  • SpringBoot查看项目配置信息的几种常见方法

    SpringBoot查看项目配置信息的几种常见方法

    这篇文章主要为大家详细介绍了查看Spring Boot项目所有配置信息的几种方法,包括 Actuator端点,日志输出,代码级获取等方式并附带详细步骤和示例,希望对大家有一定的帮助
    2025-04-04
  • 简单了解Java方法的定义和使用实现

    简单了解Java方法的定义和使用实现

    这篇文章主要给大家介绍了关于Java中方法使用的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-06-06
  • springmvc和js前端的数据传递和接收方式(两种)

    springmvc和js前端的数据传递和接收方式(两种)

    本文介绍了springmvc和js前端的数据传递和接收方式(两种),详细的介绍了两种方式,一种是json格式传递,另一种是Map传递,具有一定的参考价值,有兴趣的可以了解一下
    2017-12-12

最新评论