第3章

🔒 synchronized关键字

深入理解Java最基础的同步机制,掌握对象锁、类锁和锁升级原理

学习目标

synchronized原理

synchronized是Java提供的最基础的同步机制,它基于监视器锁(Monitor Lock)实现。每个Java对象都有一个内置的监视器锁,当线程进入synchronized代码块或方法时,会自动获取这个锁。

核心概念

synchronized关键字保证了同一时刻只有一个线程可以执行被同步的代码,从而确保线程安全。

监视器锁机制

获取锁
线程进入synchronized代码块时,尝试获取对象的监视器锁。如果锁可用,则获取锁并继续执行。
等待锁
如果锁被其他线程持有,当前线程会被阻塞,进入等待状态,直到锁被释放。
释放锁
线程执行完synchronized代码块后,会自动释放锁,唤醒等待的线程。

字节码分析

从字节码层面看,synchronized的实现使用了monitorenter和monitorexit指令:

字节码示例
// Java代码
public void synchronizedMethod() {
    synchronized(this) {
        // 同步代码块
    }
}

// 对应字节码
monitorenter    // 获取锁
// 同步代码
monitorexit     // 释放锁
monitorexit     // 异常情况下的释放锁

对象锁和类锁

synchronized可以修饰实例方法、静态方法和代码块,根据修饰的目标不同,获取的锁也不同。

对象锁(实例锁)

实例方法同步
示例代码
public synchronized void instanceMethod() {
    // 获取当前对象的锁
}
代码块同步
示例代码
public void method() {
    synchronized(this) {
        // 获取当前对象的锁
    }
}

类锁(静态锁)

静态方法同步
示例代码
public static synchronized void staticMethod() {
    // 获取类的Class对象的锁
}
Class对象同步
示例代码
public void method() {
    synchronized(MyClass.class) {
        // 获取类的Class对象的锁
    }
}
重要区别

对象锁和类锁是完全独立的,一个线程获取了对象锁,不会阻止其他线程获取类锁。

锁升级机制

为了提高性能,JVM对synchronized进行了优化,引入了锁升级机制。锁会根据竞争情况从偏向锁升级到轻量级锁,再升级到重量级锁。

偏向锁
  • 适用于只有一个线程访问的情况
  • 几乎没有性能开销
  • 在对象头中记录线程ID
  • 当有其他线程竞争时升级
轻量级锁
  • 适用于线程交替执行的情况
  • 使用CAS操作避免阻塞
  • 在栈帧中创建锁记录
  • 竞争激烈时升级为重量级锁
重量级锁
  • 适用于高竞争的情况
  • 使用操作系统的互斥量
  • 线程会被阻塞和唤醒
  • 性能开销最大
升级过程

偏向锁 → 轻量级锁 → 重量级锁(单向升级,不可降级)

死锁问题

死锁是多线程编程中的经典问题,当两个或多个线程相互等待对方释放锁时,就会发生死锁。

死锁的四个必要条件

互斥条件
资源不能被多个线程同时使用
持有和等待
线程持有资源的同时等待其他资源
不可剥夺
资源不能被强制从线程中剥夺
循环等待
存在线程资源的循环等待链

死锁示例

经典死锁场景
public class DeadlockExample {
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();
    
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 1: 获取lock1");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                synchronized (lock2) {
                    System.out.println("Thread 1: 获取lock2");
                }
            }
        });
        
        Thread t2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread 2: 获取lock2");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                synchronized (lock1) {
                    System.out.println("Thread 2: 获取lock1");
                }
            }
        });
        
        t1.start();
        t2.start();
    }
}

死锁预防策略

性能优化

虽然synchronized使用简单,但在高并发场景下可能成为性能瓶颈。了解优化技巧有助于提升程序性能。

优化策略

减小锁粒度

将大锁拆分为多个小锁,减少锁竞争

示例
// 粗粒度锁
synchronized(this) {
    updateField1();
    updateField2();
}

// 细粒度锁
synchronized(lock1) { updateField1(); }
synchronized(lock2) { updateField2(); }
锁分离

读写分离,使用ReadWriteLock

示例
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();
锁消除

JVM会自动消除不必要的锁

示例
// 局部变量,JVM会消除同步
StringBuffer sb = new StringBuffer();
sb.append("hello");
sb.append("world");
性能建议
  • 尽量缩短同步代码块的执行时间
  • 避免在循环中使用synchronized
  • 考虑使用并发集合类替代同步集合
  • 在高并发场景下考虑使用Lock接口

实战案例:银行转账

通过银行转账的例子,演示如何正确使用synchronized解决并发安全问题。

线程安全的银行账户
public class BankAccount {
    private double balance;
    private final Object lock = new Object();
    
    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }
    
    public void deposit(double amount) {
        synchronized (lock) {
            balance += amount;
            System.out.println("存入: " + amount + ", 余额: " + balance);
        }
    }
    
    public boolean withdraw(double amount) {
        synchronized (lock) {
            if (balance >= amount) {
                balance -= amount;
                System.out.println("取出: " + amount + ", 余额: " + balance);
                return true;
            }
            return false;
        }
    }
    
    public double getBalance() {
        synchronized (lock) {
            return balance;
        }
    }
    
    // 转账方法 - 避免死锁的实现
    public static void transfer(BankAccount from, BankAccount to, double amount) {
        // 按对象hashCode排序,避免死锁
        BankAccount firstLock = from.hashCode() < to.hashCode() ? from : to;
        BankAccount secondLock = from.hashCode() < to.hashCode() ? to : from;
        
        synchronized (firstLock.lock) {
            synchronized (secondLock.lock) {
                if (from.balance >= amount) {
                    from.balance -= amount;
                    to.balance += amount;
                    System.out.println("转账成功: " + amount);
                } else {
                    System.out.println("余额不足,转账失败");
                }
            }
        }
    }
}
上一章:线程基础 返回目录 下一章:volatile关键字