第5章

🔒 同步机制详解

深入理解synchronized关键字、死锁问题、锁粒度控制和线程间通信机制

学习目标

synchronized原理

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

核心原理

synchronized的实现基于JVM的monitorenter和monitorexit指令,以及对象头中的Mark Word来存储锁信息。

监视器锁机制

对象头
每个Java对象的对象头包含Mark Word,用于存储锁状态、哈希码、GC分代年龄等信息。
监视器
每个对象都关联一个监视器,包含Owner、EntryList、WaitSet等组件。
锁升级
从偏向锁到轻量级锁再到重量级锁的升级过程,优化性能。

锁的状态转换

// synchronized的字节码实现
public synchronized void method() {
    // 方法体
}

// 等价于
public void method() {
    synchronized(this) {
        // 方法体
    }
}

// 字节码指令
monitorenter  // 获取锁
// 方法体指令
monitorexit   // 释放锁

synchronized使用方式

synchronized可以用于修饰方法和代码块,根据不同的使用方式,锁定的对象也不同。理解这些差异对于正确使用同步机制至关重要。

同步方法

public class SynchronizedMethods {
    private int count = 0;
    
    // 实例同步方法 - 锁定当前对象
    public synchronized void increment() {
        count++;
    }
    
    // 静态同步方法 - 锁定Class对象
    public static synchronized void staticMethod() {
        System.out.println("静态同步方法");
    }
    
    // 获取count值的同步方法
    public synchronized int getCount() {
        return count;
    }
}

同步代码块

public class SynchronizedBlocks {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    private int count1 = 0;
    private int count2 = 0;
    
    public void method1() {
        // 锁定当前对象
        synchronized(this) {
            count1++;
        }
    }
    
    public void method2() {
        // 锁定指定对象
        synchronized(lock1) {
            count1++;
        }
    }
    
    public void method3() {
        // 锁定Class对象
        synchronized(SynchronizedBlocks.class) {
            System.out.println("类级别同步");
        }
    }
    
    // 细粒度锁控制
    public void finegrainedLocking() {
        synchronized(lock1) {
            count1++;
        }
        
        synchronized(lock2) {
            count2++;
        }
    }
}
实例锁
synchronized(this)或实例同步方法,锁定当前对象实例,不同实例间不互斥。
类锁
synchronized(Class.class)或静态同步方法,锁定类对象,所有实例共享。
对象锁
synchronized(object),锁定指定对象,提供更灵活的锁控制。

死锁问题

死锁是多线程编程中的经典问题,当两个或多个线程相互等待对方释放资源时就会发生死锁。理解死锁的成因和预防方法是并发编程的重要技能。

死锁的四个必要条件
  • 互斥条件:资源不能被多个线程同时使用
  • 请求和保持:线程已获得资源,同时等待其他资源
  • 不可剥夺:资源不能被强制从线程中剥夺
  • 循环等待:存在线程等待链形成环路

死锁示例

public class DeadlockExample {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    
    public void method1() {
        synchronized(lock1) {
            System.out.println("Thread1: 获得lock1");
            
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            synchronized(lock2) {
                System.out.println("Thread1: 获得lock2");
            }
        }
    }
    
    public void method2() {
        synchronized(lock2) {
            System.out.println("Thread2: 获得lock2");
            
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            synchronized(lock1) {
                System.out.println("Thread2: 获得lock1");
            }
        }
    }
    
    public static void main(String[] args) {
        DeadlockExample example = new DeadlockExample();
        
        Thread t1 = new Thread(example::method1);
        Thread t2 = new Thread(example::method2);
        
        t1.start();
        t2.start();
    }
}

死锁预防策略

锁排序
为所有锁定义全局顺序,所有线程按相同顺序获取锁。
超时机制
使用tryLock(timeout)设置获取锁的超时时间。
死锁检测
定期检测系统中的死锁并采取恢复措施。
// 锁排序解决死锁
public class DeadlockSolution {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    
    // 定义锁的顺序
    private void acquireLocksInOrder(Object firstLock, Object secondLock) {
        synchronized(firstLock) {
            synchronized(secondLock) {
                // 业务逻辑
            }
        }
    }
    
    public void method1() {
        acquireLocksInOrder(lock1, lock2);
    }
    
    public void method2() {
        acquireLocksInOrder(lock1, lock2); // 相同顺序
    }
}

wait/notify机制

wait/notify机制是Java中实现线程间通信的重要方式。它允许线程在等待某个条件时释放锁,当条件满足时被其他线程唤醒。

重要提醒

wait()、notify()和notifyAll()方法必须在synchronized块或方法中调用,且必须是锁对象的方法。

生产者消费者模式

public class ProducerConsumer {
    private final Object lock = new Object();
    private Queue queue = new LinkedList<>();
    private final int MAX_SIZE = 10;
    
    // 生产者
    public void produce() {
        synchronized(lock) {
            while (queue.size() == MAX_SIZE) {
                try {
                    System.out.println("队列已满,生产者等待");
                    lock.wait(); // 释放锁并等待
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return;
                }
            }
            
            int item = new Random().nextInt(100);
            queue.offer(item);
            System.out.println("生产: " + item + ", 队列大小: " + queue.size());
            
            lock.notifyAll(); // 唤醒所有等待的线程
        }
    }
    
    // 消费者
    public void consume() {
        synchronized(lock) {
            while (queue.isEmpty()) {
                try {
                    System.out.println("队列为空,消费者等待");
                    lock.wait(); // 释放锁并等待
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return;
                }
            }
            
            int item = queue.poll();
            System.out.println("消费: " + item + ", 队列大小: " + queue.size());
            
            lock.notifyAll(); // 唤醒所有等待的线程
        }
    }
}

wait/notify方法详解

wait()
使当前线程等待,释放锁,直到被notify()或notifyAll()唤醒。
notify()
随机唤醒一个在此对象上等待的线程。
notifyAll()
唤醒所有在此对象上等待的线程。
使用注意事项
  • 总是在while循环中使用wait(),而不是if语句
  • 优先使用notifyAll()而不是notify(),避免信号丢失
  • wait()方法会抛出InterruptedException,需要正确处理
  • 确保wait()和notify()操作的是同一个对象
上一章:线程安全 返回目录 下一章:高级同步工具