第5章
🔒 同步机制详解
深入理解synchronized关键字、死锁问题、锁粒度控制和线程间通信机制
学习目标
- 深入理解synchronized关键字的原理
- 掌握synchronized的多种使用方式
- 学会分析和解决死锁问题
- 理解锁的粒度和性能影响
- 掌握wait/notify机制的使用
synchronized原理
synchronized是Java中最基本的同步机制,它基于对象监视器(Monitor)实现。每个Java对象都有一个内置的监视器锁,当线程进入synchronized代码块或方法时,会自动获取这个锁。
核心原理
synchronized的实现基于JVM的monitorenter和monitorexit指令,以及对象头中的Mark Word来存储锁信息。
监视器锁机制
对象头
每个Java对象的对象头包含Mark Word,用于存储锁状态、哈希码、GC分代年龄等信息。
监视器
每个对象都关联一个监视器,包含Owner、EntryList、WaitSet等组件。
锁升级
从偏向锁到轻量级锁再到重量级锁的升级过程,优化性能。
锁的状态转换
- 无锁状态:对象创建时的初始状态,Mark Word存储哈希码
- 偏向锁:当只有一个线程访问时,偏向该线程,减少同步开销
- 轻量级锁:多线程竞争不激烈时,使用CAS操作避免重量级锁
- 重量级锁:竞争激烈时,使用操作系统的互斥量实现
// 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()操作的是同一个对象