线程通信

线程通信:从 synchronized 到 Lock 的演进与实践

线程通信是多线程编程中的核心问题,用于协调多个线程间的执行顺序和资源共享。Java 提供了两种主要的线程通信机制:基于synchronizedwait/notify和基于LockCondition接口。本文将深入对比这两种机制,并解析其适用场景。

synchronized 与 Object 类的线程通信方法

核心方法

Java 的每个对象都继承自Object类,提供了三个关键方法用于线程间通信:

  • wait():使当前线程释放对象锁并进入等待状态,直到被其他线程唤醒;
  • notify():随机唤醒一个在该对象上等待的线程;
  • notifyAll():唤醒所有在该对象上等待的线程。

使用规则

这些方法必须在synchronized修饰的代码块或方法中调用,否则会抛出IllegalMonitorStateException

示例:交替打印 1-100

public class AlternatePrint {  
    private static final Object LOCK = new Object();  
    private static int count = 1;  

    public static void main(String[] args) {  
        Thread t1 = new Thread(() -> {  
            while (true) {  
                synchronized (LOCK) {  
                    // 检查条件,避免虚假唤醒  
                    while (count > 100) break;  
                    System.out.println(Thread.currentThread().getName() + ": " + count++);  
                    LOCK.notify(); // 唤醒其他线程  
                    try {  
                        if (count <= 100) LOCK.wait(); // 释放锁并等待  
                    } catch (InterruptedException e) {  
                        Thread.currentThread().interrupt();  
                    }  
                }  
            }  
        }, "线程1");  

        Thread t2 = new Thread(() -> {  
            while (true) {  
                synchronized (LOCK) {  
                    while (count > 100) break;  
                    System.out.println(Thread.currentThread().getName() + ": " + count++);  
                    LOCK.notify();  
                    try {  
                        if (count <= 100) LOCK.wait();  
                    } catch (InterruptedException e) {  
                        Thread.currentThread().interrupt();  
                    }  
                }  
            }  
        }, "线程2");  

        t1.start();  
        t2.start();  
    }  
}

虚假唤醒(Spurious Wakeup)

Java 规范允许wait()在没有被显式唤醒的情况下返回,称为 “虚假唤醒”。因此,正确的使用方式是将wait()放在while循环中,确保条件满足:

synchronized (obj) {  
    while (!condition) {  
        obj.wait();  
    }  
    // 执行操作  
}

Lock 与 Condition 接口的线程通信

Condition 接口简介

JDK 1.5 引入了java.util.concurrent.locks包,提供了比synchronized更灵活的锁机制。Condition接口与Lock配合使用,替代了Objectwait/notify方法,提供更细粒度的线程控制:

  • await():等价于wait()
  • signal():等价于notify()
  • signalAll():等价于notifyAll()

Condition 的优势

  • 多个等待队列:一个Lock可以创建多个Condition对象,每个对象管理独立的等待队列,实现更精准的线程唤醒;
  • 可中断等待:支持带超时的等待和可中断的等待;
  • 公平锁支持:配合公平锁实现线程按顺序获取锁。

示例:基于 Condition 的交替打印

import java.util.concurrent.locks.Condition;  
import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReentrantLock;  

public class AlternatePrintWithCondition {  
    private static final Lock LOCK = new ReentrantLock();  
    private static final Condition CONDITION = LOCK.newCondition();  
    private static int count = 1;  

    public static void main(String[] args) {  
        Thread t1 = new Thread(() -> {  
            while (true) {  
                LOCK.lock();  
                try {  
                    while (count > 100) break;  
                    System.out.println(Thread.currentThread().getName() + ": " + count++);  
                    CONDITION.signal(); // 唤醒其他线程  
                    if (count <= 100) CONDITION.await(); // 释放锁并等待  
                } catch (InterruptedException e) {  
                    Thread.currentThread().interrupt();  
                } finally {  
                    LOCK.unlock();  
                }  
            }  
        }, "线程1");  

        Thread t2 = new Thread(() -> {  
            while (true) {  
                LOCK.lock();  
                try {  
                    while (count > 100) break;  
                    System.out.println(Thread.currentThread().getName() + ": " + count++);  
                    CONDITION.signal();  
                    if (count <= 100) CONDITION.await();  
                } catch (InterruptedException e) {  
                    Thread.currentThread().interrupt();  
                } finally {  
                    LOCK.unlock();  
                }  
            }  
        }, "线程2");  

        t1.start();  
        t2.start();  
    }  
}

多条件唤醒示例:生产者 - 消费者模型

使用 synchronized 实现

public class ProducerConsumer {  
    private static final Object LOCK = new Object();  
    private static final Queue<Integer> QUEUE = new LinkedList<>();  
    private static final int MAX_SIZE = 5;  

    public static void main(String[] args) {  
        // 生产者线程  
        Thread producer = new Thread(() -> {  
            for (int i = 0; i < 10; i++) {  
                synchronized (LOCK) {  
                    // 队列满时等待  
                    while (QUEUE.size() == MAX_SIZE) {  
                        try {  
                            LOCK.wait();  
                        } catch (InterruptedException e) {  
                            Thread.currentThread().interrupt();  
                        }  
                    }  
                    QUEUE.offer(i);  
                    System.out.println("生产者生产: " + i);  
                    LOCK.notifyAll(); // 唤醒消费者  
                }  
            }  
        });  

        // 消费者线程  
        Thread consumer = new Thread(() -> {  
            while (true) {  
                synchronized (LOCK) {  
                    // 队列空时等待  
                    while (QUEUE.isEmpty()) {  
                        try {  
                            LOCK.wait();  
                        } catch (InterruptedException e) {  
                            Thread.currentThread().interrupt();  
                        }  
                    }  
                    Integer item = QUEUE.poll();  
                    System.out.println("消费者消费: " + item);  
                    if (item == 9) break; // 退出条件  
                    LOCK.notifyAll(); // 唤醒生产者  
                }  
            }  
        });  

        producer.start();  
        consumer.start();  
    }  
}

使用 Condition 实现(更清晰的等待 / 唤醒逻辑)

import java.util.concurrent.locks.Condition;  
import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReentrantLock;  

public class ProducerConsumerWithCondition {  
    private static final Lock LOCK = new ReentrantLock();  
    private static final Condition NOT_FULL = LOCK.newCondition();  
    private static final Condition NOT_EMPTY = LOCK.newCondition();  
    private static final Queue<Integer> QUEUE = new LinkedList<>();  
    private static final int MAX_SIZE = 5;  

    public static void main(String[] args) {  
        // 生产者线程  
        Thread producer = new Thread(() -> {  
            for (int i = 0; i < 10; i++) {  
                LOCK.lock();  
                try {  
                    while (QUEUE.size() == MAX_SIZE) {  
                        NOT_FULL.await(); // 队列满时等待  
                    }  
                    QUEUE.offer(i);  
                    System.out.println("生产者生产: " + i);  
                    NOT_EMPTY.signal(); // 通知消费者队列非空  
                } catch (InterruptedException e) {  
                    Thread.currentThread().interrupt();  
                } finally {  
                    LOCK.unlock();  
                }  
            }  
        });  

        // 消费者线程  
        Thread consumer = new Thread(() -> {  
            while (true) {  
                LOCK.lock();  
                try {  
                    while (QUEUE.isEmpty()) {  
                        NOT_EMPTY.await(); // 队列空时等待  
                    }  
                    Integer item = QUEUE.poll();  
                    System.out.println("消费者消费: " + item);  
                    if (item == 9) break; // 退出条件  
                    NOT_FULL.signal(); // 通知生产者队列非满  
                } catch (InterruptedException e) {  
                    Thread.currentThread().interrupt();  
                } finally {  
                    LOCK.unlock();  
                }  
            }  
        });  

        producer.start();  
        consumer.start();  
    }  
}

synchronized 与 Condition 的对比

特性 synchronized + wait/notify Lock + Condition
锁机制 内置锁,自动释放 显式锁,需手动释放(finally 块)
等待队列数量 单一队列,无法区分唤醒类型 可创建多个 Condition,精准唤醒
公平性 非公平锁 支持公平锁
中断响应 不支持中断等待 支持可中断等待
超时机制 仅支持带超时的 wait 支持灵活的超时设置
锁状态查询 无法查询 可查询锁状态
代码复杂度 较低,语法简洁 较高,需手动管理锁

最佳实践建议

1. 选择原则

  • 优先使用 synchronized:简单场景、锁竞争不激烈时,语法简洁,JVM 对其有优化;
  • 使用 Lock+Condition:需要灵活的锁控制(如多条件唤醒、可中断锁、公平锁)。

2. 注意事项

  • 避免死锁:确保线程按相同顺序获取锁,并在 finally 块中释放锁;
  • 防范虚假唤醒:始终使用while循环检查条件;
  • 性能考量:高并发场景下,ReentrantLock可能提供更好的性能,但需根据实际情况测试。