Java 线程同步-03:synchronized 机制

前言

Java的 synchronized 关键字是 Java 并发编程中最基础、最常用的线程同步机制,它的主要作用是保证多线程在访问共享资源时的线程安全。

采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。

本文的重点是 synchronized 原理,如果你对其他章节不感兴趣可以直接阅读第三章节。

使用方式

synchronized 可以锁定不同的对象,主要是实例级别和类级别两个方面:

锁定对象类型 语法形式 锁对象 作用范围
实例锁 synchronized 实例方法 this(当前实例) 当前实例的所有同步实例方法
特定对象锁 synchronized(object) 任意对象(字段、局部变量等) 锁定特定代码块
静态锁 synchronized static 方法 类对象(Class对象) 类的所有实例的所有静态同步方法
类字面量锁 synchronized(ClassName.class) 类对象(Class对象) 锁定特定代码块
字符串字面量锁 synchronized("LOCK") 字符串常量池中的对象 全局范围的锁
参数对象锁 synchronized(param) 方法参数对象 根据参数对象确定

代码示例:

java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * synchronized 锁定不同对象的完整示例
 * 演示6种不同的锁定方式及其并发行为
 */
public class SynchronizedDifferentLocksDemo {
    
    // ========== 各种锁对象定义 ==========
    private final Object customLock1 = new Object();   // 特定对象锁1
    private final Object customLock2 = new Object();   // 特定对象锁2
    private final String lockKey = "USER_";           // 字符串锁前缀
    private int instanceCounter = 0;                  // 实例变量
    private static int staticCounter = 0;             // 静态变量
    
    
    // ========== 1. 实例锁 ==========
    /**
     * 方式1:实例方法锁 - 锁定当前实例 (this)
     * 作用:保护实例变量,同一实例的方法调用会互斥
     */
    public synchronized void instanceMethodLock() {
        System.out.println(Thread.currentThread().getName() 
            + " 进入实例方法锁,保护实例变量: " + instanceCounter);
        try {
            instanceCounter++;
            Thread.sleep(300); // 模拟业务处理
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println(Thread.currentThread().getName() 
            + " 离开实例方法锁,实例变量: " + instanceCounter);
    }
    
    /**
     * 方式2:同步代码块锁定当前实例 - 等价于实例方法锁
     */
    public void instanceBlockLock() {
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() 
                + " 进入实例代码块锁");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println(Thread.currentThread().getName() 
                + " 离开实例代码块锁");
        }
    }
    
    
    // ========== 2. 特定对象锁 ==========
    /**
     * 方式3:锁定特定对象1 - 细粒度锁
     * 作用:减少锁竞争,提高并发度
     */
    public void customObjectLock1() {
        synchronized (customLock1) {
            System.out.println(Thread.currentThread().getName() 
                + " 进入特定对象锁1");
            try {
                Thread.sleep(250);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println(Thread.currentThread().getName() 
                + " 离开特定对象锁1");
        }
    }
    
    /**
     * 方式4:锁定特定对象2 - 与锁1不同,可以并行执行
     */
    public void customObjectLock2() {
        synchronized (customLock2) {
            System.out.println(Thread.currentThread().getName() 
                + " 进入特定对象锁2");
            try {
                Thread.sleep(250);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println(Thread.currentThread().getName() 
                + " 离开特定对象锁2");
        }
    }
    
    
    // ========== 3. 静态锁 ==========
    /**
     * 方式5:静态方法锁 - 锁定类对象
     * 作用:保护静态变量,所有实例共享同一把锁
     */
    public static synchronized void staticMethodLock() {
        System.out.println(Thread.currentThread().getName() 
            + " 进入静态方法锁,保护静态变量: " + staticCounter);
        try {
            staticCounter++;
            Thread.sleep(350);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println(Thread.currentThread().getName() 
            + " 离开静态方法锁,静态变量: " + staticCounter);
    }
    
    
    // ========== 4. 类字面量锁 ==========
    /**
     * 方式6:类字面量锁 - 锁定类对象,与静态锁等价
     * 作用:类级别互斥
     */
    public void classLiteralLock() {
        synchronized (SynchronizedDifferentLocksDemo.class) {
            System.out.println(Thread.currentThread().getName() 
                + " 进入类字面量锁");
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println(Thread.currentThread().getName() 
                + " 离开类字面量锁");
        }
    }
    
    
    // ========== 5. 字符串字面量锁 ==========
    /**
     * 方式7:字符串字面量锁(慎用)
     * 问题:字符串常量池共享,可能导致意外的锁竞争
     */
    public void stringLiteralLock(String userId) {
        // 使用字符串常量作为锁 - 不推荐!
        String lock = lockKey + userId;
        synchronized (lock.intern()) {  // intern()返回常量池中的引用
            System.out.println(Thread.currentThread().getName() 
                + " 进入字符串锁,用户: " + userId);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println(Thread.currentThread().getName() 
                + " 离开字符串锁,用户: " + userId);
        }
    }
    
    
    // ========== 6. 参数对象锁 ==========
    /**
     * 方式8:参数对象锁 - 动态锁
     * 作用:根据传入对象决定锁粒度
     */
    public void parameterObjectLock(Object lockObject) {
        synchronized (lockObject) {
            System.out.println(Thread.currentThread().getName() 
                + " 进入参数对象锁,对象: " + lockObject.getClass().getSimpleName());
            try {
                Thread.sleep(150);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println(Thread.currentThread().getName() 
                + " 离开参数对象锁");
        }
    }
    
    
    // ========== 测试方法 ==========
    public static void main(String[] args) throws InterruptedException {
        SynchronizedDifferentLocksDemo demo1 = new SynchronizedDifferentLocksDemo();
        SynchronizedDifferentLocksDemo demo2 = new SynchronizedDifferentLocksDemo();
        
        System.out.println("=================== 测试开始 ===================\n");
        
        // 测试1:实例锁 - 不同实例可以并行
        System.out.println("测试1:实例锁 - 不同实例的实例锁不互斥");
        ExecutorService executor1 = Executors.newFixedThreadPool(2);
        executor1.execute(() -> demo1.instanceMethodLock());
        executor1.execute(() -> demo2.instanceMethodLock()); // 可以并行
        executor1.shutdown();
        executor1.awaitTermination(2, TimeUnit.SECONDS);
        
        // 测试2:实例锁 - 同一实例会互斥
        System.out.println("\n测试2:实例锁 - 同一实例的实例锁互斥");
        ExecutorService executor2 = Executors.newFixedThreadPool(2);
        executor2.execute(() -> demo1.instanceMethodLock());
        executor2.execute(() -> demo1.instanceBlockLock()); // 会互斥
        executor2.shutdown();
        executor2.awaitTermination(2, TimeUnit.SECONDS);
        
        // 测试3:不同特定对象锁可以并行
        System.out.println("\n测试3:不同特定对象锁可以并行执行");
        ExecutorService executor3 = Executors.newFixedThreadPool(3);
        executor3.execute(() -> demo1.customObjectLock1());
        executor3.execute(() -> demo1.customObjectLock2()); // 可以并行
        executor3.execute(() -> demo1.customObjectLock1()); // 会互斥
        executor3.shutdown();
        executor3.awaitTermination(2, TimeUnit.SECONDS);
        
        // 测试4:静态锁 - 所有实例共享
        System.out.println("\n测试4:静态锁 - 所有实例共享同一把锁");
        ExecutorService executor4 = Executors.newFixedThreadPool(3);
        executor4.execute(() -> staticMethodLock());
        executor4.execute(() -> SynchronizedDifferentLocksDemo.staticMethodLock());
        executor4.execute(() -> demo1.classLiteralLock()); // 与静态锁互斥
        executor4.shutdown();
        executor4.awaitTermination(3, TimeUnit.SECONDS);
        
        // 测试5:字符串字面量锁(演示问题)
        System.out.println("\n测试5:字符串字面量锁 - 相同字符串会互斥");
        ExecutorService executor5 = Executors.newFixedThreadPool(4);
        executor5.execute(() -> demo1.stringLiteralLock("001"));
        executor5.execute(() -> demo1.stringLiteralLock("002")); // 不同字符串,可以并行
        executor5.execute(() -> demo2.stringLiteralLock("001")); // 相同字符串,会互斥!
        executor5.execute(() -> demo2.stringLiteralLock("001")); // 会互斥
        executor5.shutdown();
        executor5.awaitTermination(3, TimeUnit.SECONDS);
        
        // 测试6:参数对象锁
        System.out.println("\n测试6:参数对象锁 - 根据参数对象决定");
        ExecutorService executor6 = Executors.newFixedThreadPool(4);
        Object param1 = new Object();
        Object param2 = new Object();
        
        executor6.execute(() -> demo1.parameterObjectLock(param1));
        executor6.execute(() -> demo1.parameterObjectLock(param1)); // 相同对象,会互斥
        executor6.execute(() -> demo1.parameterObjectLock(param2)); // 不同对象,可以并行
        executor6.execute(() -> demo1.parameterObjectLock(new Object())); // 新对象,可以并行
        executor6.shutdown();
        executor6.awaitTermination(2, TimeUnit.SECONDS);
        
        // 测试7:混合锁场景
        System.out.println("\n测试7:混合锁场景 - 展示各种锁的并发关系");
        ExecutorService executor7 = Executors.newFixedThreadPool(6);
        
        // 这些方法都可以并行执行,因为锁对象不同
        executor7.execute(() -> demo1.instanceMethodLock());      // 锁 this
        executor7.execute(() -> demo1.customObjectLock1());       // 锁 customLock1
        executor7.execute(() -> demo1.customObjectLock2());       // 锁 customLock2
        executor7.execute(() -> staticMethodLock());              // 锁 Class对象
        executor7.execute(() -> demo1.stringLiteralLock("003"));  // 锁字符串
        executor7.execute(() -> demo1.parameterObjectLock(new Object())); // 锁新对象
        
        executor7.shutdown();
        executor7.awaitTermination(3, TimeUnit.SECONDS);
        
        System.out.println("\n=================== 测试完成 ===================");
        System.out.println("最终实例变量: " + demo1.instanceCounter);
        System.out.println("最终静态变量: " + staticCounter);
    }
}

synchronized 原理

synchronized 是Java内置的互斥锁,基于 JVM 的 Monitor(管程)机制 实现。对于什么Monitor机制,我们在前面的【Java对象内存布局和Monitor机制】已经介绍过了,下面再给出Monitor类的结构进行回顾下:

java 复制代码
// Monitor 内部结构
class ObjectMonitor {
    Thread owner;           // 持有锁的线程
    int count;             // 重入次数
    WaitSet waitSet;       // 等待集合(调用wait()的线程)
    EntryList entryList;   // 阻塞队列(等待锁的线程)
    
    void enter() { ... }   // 获取锁
    void exit() { ... }    // 释放锁
    void wait() { ... }    // 等待
    void notify() { ... }  // 通知
}

接下来主要介绍下在使用synchronized获取同步锁的时候,发生了什么事情。

执行过程中的三个层面

synchronized 执行过程中主要会经过如下三个层面:

text 复制代码
synchronized 执行过程:
┌─────────────────────────────────────────────────────────────┐
│                   字节码层面 (Java 编译器生成)                 │
├─────────────────────────────────────────────────────────────┤
│ 同步方法:添加 ACC_SYNCHRONIZED 标志                           │
│ 同步块:生成 monitorenter/monitorexit 指令                     │
│ 异常表:确保异常时释放锁                                        │
└─────────────────────────────────────────────┬───────────────┘
                                              │ JIT 编译优化
┌─────────────────────────────────────────────▼───────────────┐
│                 运行时层面 (JVM 解释执行/JIT)                  │
├─────────────────────────────────────────────────────────────┤
│ 解释执行:调用 InterpreterRuntime::monitorenter/exit           │
│ JIT编译:根据锁状态生成不同机器码                                │
│ 锁优化:锁消除、锁粗化、偏向锁、轻量级锁                           │
└─────────────────────────────────────────────┬───────────────┘
                                              │ 硬件执行
┌─────────────────────────────────────────────▼───────────────┐
│                    操作系统层面 (重量级锁)                     │
├─────────────────────────────────────────────────────────────┤
│ 线程阻塞/唤醒:系统调用 pthread_mutex_lock/unlock               │
│ 上下文切换:用户态 ↔ 内核态切换                                  │
│ 调度延迟:等待队列管理                                          │
└─────────────────────────────────────────────────────────────┘

字节码生成标志

在使用synchronized 时,会在字节码层面进行增强,对于同步方法和同步对象会有不同的表现:

java 复制代码
// 源码对比
public class SynchronizedBytecode {
    
    // 方式1:同步方法(隐式锁)
    public synchronized void syncMethod() {
        System.out.println("synchronized method");
    }
    
    // 方式2:同步代码块(显式锁)
    public void syncBlock() {
        synchronized(this) {
            System.out.println("synchronized block");
        }
    }
}

编译后的字节码:

java 复制代码
// 编译命令:javap -v -p SynchronizedBytecode
public class SynchronizedBytecode {
  
  // syncMethod 的字节码 - 方法级同步
  public synchronized void syncMethod();
    descriptor: ()V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED  // ← 关键标志
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3  // String "synchronized method"
         5: invokevirtual #4  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
  
  // syncBlock 的字节码 - 代码块级同步
  public void syncBlock();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0                           // 加载 this 引用到操作数栈
         1: dup                               // 复制栈顶值(this)
         2: astore_1                          // 存储到局部变量表 slot 1
         3: monitorenter                      // ← 关键指令:进入监视器
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #5                  // String "synchronized block"
         9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: aload_1                           // 加载锁对象
        13: monitorexit                       // ← 正常退出监视器
        14: goto          22                  // 跳转到方法结束
        17: astore_2                          // 异常处理开始:存储异常到 slot 2
        18: aload_1                           // 加载锁对象
        19: monitorexit                       // ← 异常退出监视器
        20: aload_2                           // 重新加载异常
        21: athrow                            // 抛出异常
        22: return
    Exception table:                          // 异常表
       from    to  target type
           4    14    17   any               // 监控4-14行,发生异常跳转到17行
          17    20    17   any               // 监控17-20行,发生异常跳转到17行(重新处理)
}

这里对实例进行同步时会增加monitorentermonitorexit 两个指令,相对比较好理解其实就是对monitor对象的获取。对方法级别进行上锁的时候会有点不一样,这里会对方法的访问标志位即acc flags增加一个ACC_SYNCHRONIZED 标志位,方法调用的时候会尝试去获取当前方法对应实例对象的monitor。

这里对实例对象上锁的时候,可以注意到存在两个monitorexit,主要是为了发生异常也能释放锁。

monitor enter&&exit

这里参考 HotSpot JVM 源码给出一个简化版的monitor获取和释放代码,仅供参考:

cpp 复制代码
class InterpreterRuntime {
    
    // monitorenter 的 C++ 实现
    static void monitorenter(JavaThread* thread, BasicObjectLock* elem) {
        Handle h_obj(thread, elem->obj());
        
        // 尝试快速获取锁
        if (UseBiasedLocking) {
            // 偏向锁处理
            if (BiasedLocking::fast_enter(h_obj, elem, true, thread)) {
                return;
            }
        }
        
        // 慢速路径
        slow_enter(h_obj, elem, thread);
    }
    
    // monitorexit 的 C++ 实现
    static void monitorexit(JavaThread* thread, BasicObjectLock* elem) {
        Handle h_obj(thread, elem->obj());
        
        // 尝试快速释放锁
        if (UseBiasedLocking) {
            if (BiasedLocking::fast_exit(h_obj, elem, thread)) {
                return;
            }
        }
        
        // 慢速路径
        slow_exit(h_obj, elem, thread);
    }
    
    // 慢速进入锁的流程
    static void slow_enter(Handle obj, BasicObjectLock* lock, JavaThread* thread) {
        markOop mark = obj->mark();
        
        // 检查是否无锁
        if (mark->is_unlocked()) {
            // 尝试获取轻量级锁
            lock->set_displaced_header(mark);
            if (mark == obj()->cas_set_mark(markOopDesc::encode(lock), mark)) {
                return; // 轻量级锁获取成功
            }
        } else if (mark->has_locker() && thread->is_lock_owned((address)mark->locker())) {
            // 可重入情况
            lock->set_displaced_header(NULL);
            return;
        }
        
        // 需要膨胀为重量级锁
        inflate(thread, obj(), inflate_cause_monitor_enter)->enter(thread);
    }
}

可重入

可重入锁 是指同一个线程可以多次获取同一把锁,而不会导致死锁。在Java中,synchronized本身就是可重入的,在有些场景下会需要可重入锁,下面简单举例:

场景:递归调用

java 复制代码
public class RecursiveDemo {
    public synchronized void methodA() {
        System.out.println("进入 methodA");
        methodB();  // 递归调用,需要重入锁
        System.out.println("离开 methodA");
    }
    
    public synchronized void methodB() {
        System.out.println("进入 methodB");
        // 如果没有可重入,这里会死锁!
    }
}

场景:继承父类方法

java 复制代码
class Parent {
    public synchronized void parentMethod() {
        System.out.println("父类方法");
    }
}

class Child extends Parent {
    @Override
    public synchronized void parentMethod() {
        super.parentMethod();  // 调用父类同步方法
        System.out.println("子类方法");
    }
}

注意点

  1. 如果同一个方法内同时有两个或更多线程,则每个线程有自己的局部变量拷贝。

  2. 类的每个实例都有自己的对象级别锁。当一个线程访问实例对象中的synchronized同步代码块或同步方法时,该线程便获取了该实例的对象级别锁,其他线程这时如果要访问synchronized同步代码块或同步方法,便需要阻塞等待,直到前面的线程从同步代码块或方法中退出,释放掉了该对象级别锁。

  3. 访问同一个类的不同实例对象中的同步代码块,不存在阻塞等待获取对象锁的问题,因为它们获取的是各自实例的对象级别锁,相互之间没有影响。

  4. 持有一个对象级别锁不会阻止该线程被交换出来,也不会阻塞其他线程访问同一示例对象中的非synchronized代码。当一个线程A持有一个对象级别锁(即进入了synchronized修饰的代码块或方法中)时,线程也有可能被交换出去,此时线程B有可能获取执行该对象中代码的时间,但它只能执行非同步代码(没有用synchronized修饰),当执行到同步代码时,便会被阻塞,此时可能线程规划器又让A线程运行,A线程继续持有对象级别锁,当A线程退出同步代码时(即释放了对象级别锁),如果B线程此时再运行,便会获得该对象级别锁,从而执行synchronized中的代码。

  5. 持有对象级别锁的线程会让其他线程阻塞在所有的synchronized代码外。例如,在一个类中有三个synchronized方法a,b,c,当线程A正在执行一个实例对象M中的方法a时,它便获得了该对象级别锁,那么其他的线程在执行同一实例对象(即对象M)中的代码时,便会在所有的synchronized方法处阻塞,即在方法a,b,c处都要被阻塞,等线程A释放掉对象级别锁时,其他的线程才可以去执行方法a,b或者c中的代码,从而获得该对象级别锁。

  6. 使用synchronized(obj)同步语句块,可以获取指定对象上的对象级别锁。obj为对象的引用,如果获取了obj对象上的对象级别锁,在并发访问obj对象时时,便会在其synchronized代码处阻塞等待,直到获取到该obj对象的对象级别锁。当obj为this时,便是获取当前对象的对象级别锁。

  7. 类级别锁被特定类的所有示例共享,它用于控制对static成员变量以及static方法的并发访问。具体用法与对象级别锁相似。

  8. 互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。根据虚拟机规范的要求,在执行monitorenter指令时,首先要尝试获取对象的锁,如果获得了锁,把锁的计数器加1,相应地,在执行monitorexit指令时会将锁计数器减1,当计数器为0时,锁便被释放了。由于synchronized同步块对同一个线程是可重入的,因此一个线程可以多次获得同一个对象的互斥锁,同样,要释放相应次数的该互斥锁,才能最终释放掉该锁。

  9. 实例锁和类锁不存在冲突,因此可以并行执行。

相关推荐
Lyda2 小时前
i18n Ally Next:重新定义 VS Code 国际化开发体验
前端·javascript·后端
橙序员小站2 小时前
程序员如何做好年夜饭:用系统设计思维搞定一桌硬菜
前端·后端
Eric2232 小时前
CLI-Agent-Manager:面向 Vibe Coding 的多 Agent 统一管理面板
人工智能·后端·开源
消失的旧时光-19432 小时前
第二十二课:领域建模入门——从业务中“提炼结构”(认知篇)
java·spring boot·后端·domain
golang学习记2 小时前
Spring Boot 4 升级实战:从3.x到4.0的分步升级保姆级指南
java·spring boot·后端
sheji70092 小时前
Springboot家教平台中心系统53754--(程序+源码+数据库+调试部署+开发环境)
java·数据库·spring boot·后端·spring·旅游
AskHarries2 小时前
GitHub Login 对接配置指南
后端
xuzhiqiang07243 小时前
【wiki知识库】07.用户管理后端SpringBoot部分
spring boot·后端·状态模式
谭光志3 小时前
OpenClaw 安装与运行教程
前端·后端·ai编程