深入理解 synchronized:到底锁的是谁?

在 Java 多线程并发编程里,synchronized 是保证线程安全的核心关键字,但很多开发者只知道它能加锁,却不清楚它到底锁的是什么不同写法锁的范围有何区别

这篇文章就把 synchronized 的锁对象、作用范围、经典测试用例一次性讲透,代码与测试结果完全还原,帮你彻底搞懂 synchronized 的 "锁事"。


一、synchronized 锁的核心结论

synchronized 本质锁的是对象,而非代码!不同写法对应不同锁对象:

  1. 修饰实例方法 → 锁 当前实例对象(this)
  2. 修饰静态方法 → 锁 当前类的 Class 对象
  3. 修饰代码块 → 锁 括号内指定的对象

只有锁的是同一个对象,多线程才会互斥等待;锁不同对象,互不影响。


二、完整测试用例(一模一样还原)

测试用例 1:synchronized 修饰实例方法(锁 this)

代码

复制代码
public class SyncInstanceMethod {
    // 同步实例方法:锁当前对象 this
    public synchronized void test() {
        System.out.println(Thread.currentThread().getName() + " 进入同步方法");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 退出同步方法");
    }

    public static void main(String[] args) {
        // 同一个对象
        SyncInstanceMethod instance = new SyncInstanceMethod();

        new Thread(() -> instance.test(), "线程A").start();
        new Thread(() -> instance.test(), "线程B").start();
    }
}

执行结果

复制代码
线程A 进入同步方法
线程A 退出同步方法
线程B 进入同步方法
线程B 退出同步方法

结论

同一个实例对象的同步方法,多线程互斥执行,锁是当前实例 this。


测试用例 2:不同实例调用实例同步方法(互不锁)

代码

复制代码
public class SyncInstanceMethodDiff {
    public synchronized void test() {
        System.out.println(Thread.currentThread().getName() + " 进入同步方法");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 退出同步方法");
    }

    public static void main(String[] args) {
        // 两个不同实例
        SyncInstanceMethodDiff instance1 = new SyncInstanceMethodDiff();
        SyncInstanceMethodDiff instance2 = new SyncInstanceMethodDiff();

        new Thread(() -> instance1.test(), "线程A").start();
        new Thread(() -> instance2.test(), "线程B").start();
    }
}

执行结果

复制代码
线程A 进入同步方法
线程B 进入同步方法
线程A 退出同步方法
线程B 退出同步方法

结论

不同实例的同步方法,锁对象不同,不互斥,并发执行


测试用例 3:synchronized 修饰静态方法(锁 Class 对象)

代码

复制代码
public class SyncStaticMethod {
    // 同步静态方法:锁当前类 Class 对象
    public static synchronized void test() {
        System.out.println(Thread.currentThread().getName() + " 进入静态同步方法");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 退出静态同步方法");
    }

    public static void main(String[] args) {
        new Thread(() -> SyncStaticMethod.test(), "线程A").start();
        new Thread(() -> SyncStaticMethod.test(), "线程B").start();
    }
}

执行结果

复制代码
线程A 进入静态同步方法
线程A 退出静态同步方法
线程B 进入静态同步方法
线程B 退出静态同步方法

结论

静态同步方法锁的是类的 Class 对象,全局唯一,所有线程互斥。


测试用例 4:synchronized 修饰代码块(指定锁对象)

代码

复制代码
public class SyncBlock {
    // 自定义锁对象
    private final Object lock = new Object();

    public void test() {
        // 同步代码块:锁 lock 对象
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " 进入同步代码块");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 退出同步代码块");
        }
    }

    public static void main(String[] args) {
        SyncBlock instance = new SyncBlock();
        new Thread(() -> instance.test(), "线程A").start();
        new Thread(() -> instance.test(), "线程B").start();
    }
}

执行结果

复制代码
线程A 进入同步代码块
线程A 退出同步代码块
线程B 进入同步代码块
线程B 退出同步代码块

结论

同步代码块锁指定对象,粒度更灵活,是企业开发推荐写法。


测试用例 5:实例锁 VS 类锁(互不干扰)

代码

复制代码
public class SyncInstanceAndClass {
    // 实例方法锁:this
    public synchronized void instanceMethod() {
        System.out.println(Thread.currentThread().getName() + " 进入实例方法");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 退出实例方法");
    }

    // 静态方法锁:Class
    public static synchronized void staticMethod() {
        System.out.println(Thread.currentThread().getName() + " 进入静态方法");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 退出静态方法");
    }

    public static void main(String[] args) {
        SyncInstanceAndClass instance = new SyncInstanceAndClass();
        new Thread(() -> instance.instanceMethod(), "线程A").start();
        new Thread(() -> SyncInstanceAndClass.staticMethod(), "线程B").start();
    }
}

执行结果

复制代码
线程A 进入实例方法
线程B 进入静态方法
线程A 退出实例方法
线程B 退出静态方法

结论

实例锁(this)和类锁(Class)是两把完全独立的锁,互不阻塞。


三、一张表总结 synchronized 锁对象

写法 锁对象 作用范围
实例方法 synchronized 当前实例 this 同一实例互斥
静态方法 synchronized 当前类 Class 全局所有实例互斥
同步代码块 synchronized (obj) 指定对象 obj 仅锁 obj 范围

四、核心要点回顾

  1. synchronized 永远锁对象,不锁代码。
  2. 只有锁同一个对象,才会互斥。
  3. 实例锁和类锁是两把独立锁,互不影响。
  4. 代码块锁粒度最细,性能最优,优先使用。

搞懂 synchronized 到底锁谁,是写出高并发、线程安全代码的第一步!

相关推荐
杨云龙UP2 小时前
MySQL 数据库常用备份工具对比:mysqldump、mydumper 与 XtraBackup
linux·运维·服务器·数据库·mysql·dba·备份恢复
PC2005-cloud2 小时前
从零上手 openGauss:Docker 部署 + 全场景连接教程
运维·docker·容器
独隅2 小时前
Cursor+GitOps 自动化运维实战:智能编写 Ansible 与 Terraform
运维·自动化·ansible
happymagic3 小时前
java spring boot做的jar包程序,如何实现自动运行启动
java·运维·服务器·spring boot·jar
星空你好3 小时前
AI辅助运维:开源一个内网日志监控工具
运维
风曦Kisaki3 小时前
Kubernetes(K8s)笔记Day06: 持久化存储(emptyDir,hostPath,NFS),PV 和 PVC,StorageClass存储类
linux·运维·笔记·云原生·容器·kubernetes
Dr.kangder4 小时前
嵌入式处理器仿真技术——Hypervisor 原理与实践
服务器·嵌入式硬件·架构·嵌入式
星蓝_starblue5 小时前
零服务器、零数据库!开源growth-board,利用GitHub自动管理刷题/学习/求职全流程
服务器·数据库·程序人生·系统架构·node.js·github·改行学it
回眸&啤酒鸭7 小时前
【回眸】搞钱灵感——自动化敏捷开发项目管理系统落地设计
运维·自动化·敏捷流程
Crazy________8 小时前
k8s部署若依微服务架构流程,v3.6.6
运维·云原生·容器·kubernetes·状态模式