深入理解 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 到底锁谁,是写出高并发、线程安全代码的第一步!

相关推荐
Michelle80235 小时前
第1周——认清考试与IDLE生存指南
运维·服务器
严同学正在努力5 小时前
PostgreSQL 高可用教程(流复制 + Patroni 生产级实战)
运维·数据库·ai·postgresql·架构
Soneswy6 小时前
Linux系统篇--用户切换与文件权限
linux·服务器
Ruiery6 小时前
Linux 6.6内核内存管理深度解析(三·补):页表项(PTE)结构与地址翻译
linux·运维·服务器
阿狸猿6 小时前
AI Agent自动写自动化脚本
运维·人工智能·自动化
五块钱的方便面6 小时前
ubuntu下networkmanager不让管理解决办法
linux·运维·ubuntu
云飞云共享云桌面6 小时前
苏州半导体工厂研发部门多个SolidWorks 如何共享服务器来进行研发设计
运维·服务器·3d·自动化·制造
呉師傅6 小时前
Win7剪切文件变成复制文件如何修改【资源管理器异常类故障】
运维·网络·windows·电脑
honsor6 小时前
机房动环实战:H-THDRJ45 双网口温湿度变送器 Modbus RTU RS485 冗余组网方案
运维·服务器·网络
快乐的韭菜根根6 小时前
Slurm 指定 GPU 卡:`File=/dev/nvidiaX` 映射的坑与修复
java·服务器·网络