Java并发编程:使用Condition类实现精确线程通信

一只小鹿
一只小鹿
发布时间:2026-10-14| 4人浏览过
原创

Condition是JDK 5引入的高级线程通信工具,它允许将多个等待集绑定到同一个锁上,从而解决传统synchronized关键字无法区分通知对象的缺陷。本文将深入解析Condition的工作机制,并提供基于ReentrantLock的实战代码。

Condition与传统监视器的区别

在Java中,Object类的wait()、notify()和notifyAll()方法用于同步对象,但它们必须与"同步锁"(即synchronized关键字)捆绑使用,且每个对象只有一个等待队列。这导致程序难以针对特定条件的线程进行唤醒,容易引发虚假唤醒或唤醒错误的线程。

Condition类打破了这种限制。Condition需要与"互斥锁"(如java.util.concurrent.locks.Lock)组合使用。其核心优势在于:

  • 解耦关联:一个Lock可以创建多个Condition实例,每个Condition对应特定的业务逻辑或线程状态。
  • 精确唤醒:通过signal()唤醒指定条件队列中的一个线程,或者通过signalAll()唤醒全部,避免了全局干扰。

核心方法对照表

理解Condition的功能,最直接的方式是与Object方法进行对比:

// 造成当前线程在接到信号或被中断之前一直处于等待状态。
void await()
// 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
boolean await(long time, TimeUnit unit)
// 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
long awaitNanos(long nanosTimeout)
// 造成当前线程在接到信号之前一直处于等待状态。(不可中断)
void awaitUninterruptibly()
// 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。
boolean awaitUntil(Date deadline)
// 唤醒一个等待线程。
void signal()
// 唤醒所有等待线程。
void signalAll()

基础用法示例

以下示例展示了如何使用Condition替代synchronized进行简单的线程交替执行。相比传统的notify/notifyAll,这里的逻辑更清晰,lock/unlock结构也更加规范。

public class ThreadTest2 { 
  public static void main(String[] args) { 
    final Business business = new Business(); 
    // 启动子线程
    new Thread(new Runnable() { 
      @Override 
      public void run() { 
        threadExecute(business, "sub"); 
      } 
    }).start();
    // 主线程执行
    threadExecute(business, "main"); 
  }   
  
  public static void threadExecute(Business business, String threadType) { 
    for(int i = 0; i < 100; i++) { 
      try { 
        if("main".equals(threadType)) { 
          business.main(i); 
        } else { 
          business.sub(i); 
        } 
      } catch (InterruptedException e) { 
        e.printStackTrace(); 
      }
    }
  } 
}
class Business { 
  private boolean bool = true; 
  private Lock lock = new ReentrantLock(); 
  // 为Lock创建Condition对象
  private Condition condition = lock.newCondition();  
  
  public /*synchronized*/ void main(int loop) throws InterruptedException { 
    lock.lock();
    try {
      // 使用while防止虚假唤醒
      while(bool) {         
        condition.await();//相当于this.wait()
      }
      for(int i = 0; i < 100; i++) { 
        System.out.println("main thread seq of " + i + ", loop of " + loop);
      }
      bool = true;
      condition.signal();//相当于this.notify(), 唤醒sub线程
    } finally {
      lock.unlock();
    }
  }   
  
  public /*synchronized*/ void sub(int loop) throws InterruptedException { 
    lock.lock();
    try {
      while(!bool) {
        condition.await();
      }
      for(int i = 0; i < 10; i++) { 
        System.out.println("sub thread seq of " + i + ", loop of " + loop);
      }
      bool = false;
      condition.signal();//相当于this.notify(), 唤醒main线程
    } finally {
      lock.unlock();
    }
  } 
}

进阶案例:基于多Condition的有界缓存区

上述例子虽然能用,但本质上还是单Condition。Condition真正强大的地方体现在生产者-消费者模式中。当存在多个阻塞条件时(例如:缓冲区满需等待写操作完成,缓冲区空需等待读操作完成),如果使用单个Condition,可能导致唤醒错误的线程,产生性能浪费甚至死循环。

下面是一个标准的有界缓存区实现:

class BoundedBuffer { 
  final Lock lock = new ReentrantLock();//锁对象 
  final Condition notFull = lock.newCondition();//写线程条件:缓冲区不满时可写
  final Condition notEmpty = lock.newCondition();//读线程条件:缓冲区非空时可读
 
  final Object[] items = new Object[100];//缓存队列 
  int putptr/*写索引*/, takeptr/*读索引*/, count/*队列中存在的数据个数*/; 
 
  public void put(Object x) throws InterruptedException { 
   lock.lock();
   try {
    // 如果队列满了,则挂起当前写线程
    while (count == items.length)
     notFull.await();
    
    items[putptr] = x;//赋值
    // 环形数组索引处理
    if (++putptr == items.length) putptr = 0;
    ++count;//计数加一
    
    // 写入成功后,唤醒一个正在等待数据的读线程
    notEmpty.signal();
   } finally {
    lock.unlock();
   }
  }
 
  public Object take() throws InterruptedException { 
   lock.lock();
   try {
    // 如果队列为空,则挂起当前读线程
    while (count == 0)
     notEmpty.await();
    
    Object x = items[takeptr];//取值
    // 环形数组索引处理
    if (++takeptr == items.length) takeptr = 0;
    --count;//计数减一
    
    // 读取后腾出空间,唤醒一个正在等待空间的写线程
    notFull.signal();
    return x;
   } finally {
    lock.unlock();
   }
  }  
}

执行流程解析

  1. 写线程进入put方法:首先检查count == items.length,若已满,调用notFull.await()挂起自己,并释放锁。
  2. 读线程有机会进入take方法:发现数据可用,读取数据并将count--,然后调用notFull.signal()唤醒一个挂起的写线程。
  3. 写线程被唤醒:重新获得锁,跳出循环继续写入。同时,写线程结束后调用notEmpty.signal()唤醒读线程。

这种设计保证了针对性唤醒:只有在需要时才唤醒对应的线程集合。相比之下,若仅使用单一的this.notify(),当缓存区满时唤醒的是任意线程(可能是另一个写线程),该线程醒来后发现依然满而再次挂起,造成无谓的上下文切换开销。

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。