Java多线程知识小结:Synchronized

在Java中,synchronized 关键字是实现线程同步的核心工具,用于保证同一时刻只有一个线程可以执行被修饰的代码块或方法。以下从基本原理、锁升级过程、应用场景及优化建议四个维度详细解析:

一、基本原理

1. 同步的对象

synchronized 锁的是对象而非代码:

  • 修饰实例方法:锁的是当前实例对象(this);
  • 修饰静态方法:锁的是类的Class对象(如MyClass.class);
  • 修饰代码块:锁的是括号中的对象(如synchronized(obj))。
2. 底层实现
  • JVM层面:通过对象头中的Mark Word和**Monitor(监视器)**实现。
    对象头的Mark Word包含锁状态位(如偏向锁、轻量级锁、重量级锁)和指向Monitor的指针。
  • 字节码层面
    • 同步方法:通过ACC_SYNCHRONIZED标志位标识;
    • 同步代码块:通过monitorentermonitorexit指令实现。
3. 互斥锁语义
  • 线程进入同步块前需获取Monitor的所有权(锁),退出时释放锁;
  • 未获取到锁的线程会被阻塞,进入Monitor的等待队列。

二、锁升级的具体过程

JDK 6之后,synchronized 锁机制进行了优化,引入锁升级(偏向锁 → 轻量级锁 → 重量级锁)以减少性能开销。

1. 偏向锁(Biased Locking)
  • 适用场景:单线程环境,无锁竞争。
  • 原理
    1. 首次线程访问同步块时,Mark Word存储该线程ID(偏向状态);
    2. 后续该线程再次进入同步块时,无需CAS操作,直接获取锁;
    3. 若其他线程尝试竞争锁,偏向锁撤销,升级为轻量级锁。
  • 优点:无CAS操作,仅首次获取锁时有少量开销。
  • 缺点:存在锁撤销(偏向锁撤销需STW),若存在多线程竞争,反而增加开销。
2. 轻量级锁(Lightweight Locking)
  • 适用场景:多线程交替执行同步块(无实际竞争)。
  • 原理
    1. 线程进入同步块时,JVM在当前线程栈帧中创建锁记录(Lock Record)
    2. 通过CAS将对象头的Mark Word复制到锁记录,并将Mark Word指向锁记录地址;
    3. 若CAS成功,获取轻量级锁;若失败,说明存在竞争,锁膨胀为重量级锁。
  • 优点:竞争不激烈时,避免线程阻塞,减少用户态/内核态切换。
  • 缺点:竞争激烈时,频繁CAS导致性能下降。
3. 重量级锁(Heavyweight Lock)
  • 适用场景:多线程同时竞争锁。
  • 原理
    1. 锁膨胀后,Mark Word指向操作系统的Monitor对象
    2. 未获取到锁的线程被阻塞(进入内核态),放入Monitor的等待队列;
    3. 锁释放时,唤醒等待队列中的线程(需从内核态转回用户态)。
  • 缺点:线程阻塞/唤醒涉及用户态与内核态切换,性能开销大。
4. 锁升级流程图
无锁状态 → 偏向锁(单线程) → 轻量级锁(多线程交替) → 重量级锁(多线程竞争)
5. 锁升级的触发条件
  • 偏向锁撤销:当其他线程尝试访问偏向锁对象时,JVM在安全点(Safepoint)撤销偏向锁;
  • 轻量级锁膨胀:线程尝试CAS获取轻量级锁失败时,锁膨胀为重量级锁;
  • 批量重偏向/撤销:当一个类的对象频繁发生偏向锁撤销时,JVM会对该类的对象批量重偏向或撤销。

三、应用场景

1. 保护共享资源
  • 示例:多线程操作同一个计数器:
    public class Counter {
        private int count = 0;
        
        // 同步方法,锁的是this
        public synchronized void increment() {
            count++;
        }
    }
    
2. 实现原子操作
  • 示例:双重检查锁定(DCL)实现单例模式:
    public class Singleton {
        private static volatile Singleton instance; // 必须用volatile禁止指令重排
        
        public static Singleton getInstance() {
            if (instance == null) { // 第一次检查
                synchronized (Singleton.class) { // 锁的是Class对象
                    if (instance == null) { // 第二次检查
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    
3. 保证复合操作的原子性
  • 示例:检查再执行(Check-Then-Act)操作:
    public class Inventory {
        private Map<String, Integer> stock = new HashMap<>();
        
        // 复合操作:先检查库存,再扣减
        public synchronized boolean decreaseStock(String product, int amount) {
            if (stock.getOrDefault(product, 0) >= amount) {
                stock.put(product, stock.get(product) - amount);
                return true;
            }
            return false;
        }
    }
    
4. 线程间协作(wait/notify机制)
  • 示例:生产者-消费者模型:
    public class ProducerConsumer {
        private final Queue<Integer> queue = new LinkedList<>();
        private final int CAPACITY = 10;
        
        // 生产者方法
        public synchronized void produce(int item) throws InterruptedException {
            while (queue.size() == CAPACITY) {
                wait(); // 等待队列有空间
            }
            queue.add(item);
            notifyAll(); // 通知消费者有新元素
        }
        
        // 消费者方法
        public synchronized int consume() throws InterruptedException {
            while (queue.isEmpty()) {
                wait(); // 等待队列有元素
            }
            int item = queue.poll();
            notifyAll(); // 通知生产者有空间
            return item;
        }
    }
    

四、优化建议

1. 缩小同步块范围
  • 反例
    public synchronized void process() {
        // 非关键代码
        long startTime = System.currentTimeMillis();
        
        // 关键代码(需要同步)
        synchronized (this) {
            // 操作共享资源
        }
        
        // 非关键代码
        System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
    }
    
  • 正例:仅同步关键代码:
    public void process() {
        long startTime = System.currentTimeMillis();
        
        // 仅同步关键代码
        synchronized (this) {
            // 操作共享资源
        }
        
        System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
    }
    
2. 优先使用同步代码块而非同步方法
  • 同步方法默认锁的是this,可能导致锁范围过大;
  • 同步代码块可精确控制锁对象。
3. 使用细粒度锁替代粗粒度锁
  • 反例
    public class Bank {
        private Map<String, Account> accounts = new HashMap<>();
        
        // 粗粒度锁:整个方法同步
        public synchronized void transfer(String from, String to, double amount) {
            // 转账逻辑
        }
    }
    
  • 正例
    public class Bank {
        private Map<String, Account> accounts = new HashMap<>();
        
        // 细粒度锁:仅锁相关账户
        public void transfer(String from, String to, double amount) {
            Account fromAccount = accounts.get(from);
            Account toAccount = accounts.get(to);
            
            // 按顺序加锁,避免死锁
            Account first = fromAccount.hashCode() < toAccount.hashCode() ? fromAccount : toAccount;
            Account second = first == fromAccount ? toAccount : fromAccount;
            
            synchronized (first) {
                synchronized (second) {
                    // 转账逻辑
                }
            }
        }
    }
    
4. 考虑替代方案
  • 原子类(如AtomicInteger)替代简单计数器的synchronized
  • 读写锁ReentrantReadWriteLock)替代读多写少场景的synchronized
  • 并发容器(如ConcurrentHashMap)替代synchronized Map

五、总结

synchronized 关键字的核心特点:

  • 优点
    • 使用简单,无需手动释放锁;
    • 锁升级机制在不同场景下有较好的性能表现;
    • 支持线程间协作(wait/notify)。
  • 缺点
    • 无法中断正在等待锁的线程;
    • 不支持超时获取锁;
    • 锁粒度较粗(要么全锁,要么全放)。

适用场景

  • 简单的同步需求(如保护共享资源、实现原子操作);
  • 需配合wait/notify实现线程间协作。

在高并发场景下,若性能敏感,可考虑使用ReentrantLockStampedLock等更灵活的锁机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值