线程间通信总出问题?Java多线程通信实战详解

chengsenw 项目开发线程间通信总出问题?Java多线程通信实战详解已关闭评论63阅读模式

作为一名摸爬滚打5年的全栈老司机,我见过太多新手在多线程通信上栽跟头。明明代码逻辑看起来天衣无缝,运行时却莫名其妙卡死、数据错乱,甚至直接崩溃。别慌,今天咱们就用最接地气的方式,把Java多线程通信那点事儿彻底讲透。

线程间通信总出问题?Java多线程通信实战详解

一、为什么线程间需要通信?

想象一下餐厅后厨的工作场景:厨师(线程A)做好菜后,需要通知服务员(线程B)上菜。如果厨师闷头做菜不吭声,服务员可能一直在发呆,菜都凉了还没端走;如果服务员不停问"好了没",又会浪费精力。这就是典型的线程协作问题——不同线程需要按照特定顺序配合完成工作。

Java多线程通信的核心痛点集中在三点:

  • 线程执行顺序不可控(谁先谁后全看CPU心情)
  • 共享资源访问冲突(俩人同时改一个数据)
  • 状态可见性问题(A改了数据B却看不见)

二、实战:wait()/notify()的经典用法

先来看最基础的等待通知机制。下面这个例子模拟生产者-消费者场景,生产者生产数据后通知消费者,消费者处理完后通知生产者:

public class WaitNotifyDemo {
    private String message;
    private boolean empty = true;
    
    // 生产者方法
    public synchronized void produce(String msg) {
        while (!empty) {
            try {
                wait(); // 等待消费者消费
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        message = msg;
        empty = false;
        notify(); // 唤醒消费者线程
        System.out.println("生产了: " + msg);
    }
    
    // 消费者方法
    public synchronized String consume() {
        while (empty) {
            try {
                wait(); // 等待生产者生产
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        String msg = message;
        empty = true;
        notify(); // 唤醒生产者线程
        System.out.println("消费了: " + msg);
        return msg;
    }
}

关键要点:

  1. wait()和notify()必须在synchronized代码块内调用
  2. 一定要用while循环检查条件,不要用if(避免虚假唤醒)
  3. notify()随机唤醒一个线程,notifyAll()唤醒所有等待线程

三、更现代的通信方式:Lock和Condition

JDK5提供的Lock接口及其配套的Condition对象,提供了更灵活的线程通信机制:

public class LockConditionDemo {
    private final Lock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();
    private final Condition notFull = lock.newCondition();
    
    private String[] items = new String[10];
    private int putPtr, takePtr, count;
    
    public void produce(String item) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) {
                notFull.await(); // 队列满时等待
            }
            items[putPtr] = item;
            if (++putPtr == items.length) putPtr = 0;
            count++;
            notEmpty.signal(); // 唤醒消费者
        } finally {
            lock.unlock();
        }
    }
    
    public String consume() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                notEmpty.await(); // 队列空时等待
            }
            String item = items[takePtr];
            if (++takePtr == items.length) takePtr = 0;
            count--;
            notFull.signal(); // 唤醒生产者
            return item;
        } finally {
            lock.unlock();
        }
    }
}

使用Condition的优势:

  • 可以创建多个Condition实现精确通知(比如单独通知生产者或消费者)
  • 支持超时等待(awaitNanos()/awaitUntil())
  • 支持中断响应(lockInterruptibly())

四、避坑指南:常见问题及解决方案

1. 死锁问题

两个线程互相等待对方释放锁,就像两个人相遇在窄桥上都等着对方先过。

解决方案:使用tryLock()尝试获取锁,设置超时时间

if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
    try {
        if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
            try {
                // 业务逻辑
            } finally {
                lock2.unlock();
            }
        }
    } finally {
        lock1.unlock();
    }
}

2. 活锁问题

线程不断重试失败的操作,就像两个人试图让路却总是同步移动。

解决方案:引入随机退避机制

// 失败后随机等待一段时间再重试
Thread.sleep((long) (Math.random() * 100));

3. 资源竞争

多个线程同时修改共享数据导致数据不一致。

解决方案:使用线程安全容器或原子变量

// 使用ConcurrentHashMap代替HashMap
private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

// 使用AtomicInteger代替int
private AtomicInteger counter = new AtomicInteger(0);

五、最佳实践总结

  1. 优先使用高层工具:CountDownLatch、CyclicBarrier、Semaphore等并发工具能解决90%的通信需求
  2. 避免过早优化:不要一开始就用复杂锁机制,先用简单方案验证
  3. 锁粒度要小:尽量缩小同步代码块的范围
  4. 使用线程池:避免频繁创建销毁线程,推荐使用ThreadPoolExecutor
  5. 善用volatile:保证变量的可见性(但不保证原子性)

建议新手先从wait()/notify()机制练起,理解基本通信原理后再学习更高级的并发工具。多线程调试虽然麻烦,但掌握了正确方法后,你会发现它并没有想象中那么可怕。

记住:好的多线程程序不是没有bug,而是能够 predictable(可预测)和reproducible(可复现)——这才是我们追求的终极目标。

 
chengsenw
  • 本文由 chengsenw 发表于 2025年9月4日 21:13:03
  • 转载请务必保留本文链接:https://www.gewo168.com/3040.html