• 【Java 基础篇】Java Condition 接口详解


    在这里插入图片描述

    Java 提供了一种更灵活和高级的线程协作机制,通过 Condition 接口的使用,你可以更精细地控制线程的等待和唤醒,实现更复杂的线程同步和通信。本文将详细介绍 Java 的 Condition 接口,包括它的基本概念、常见用法以及注意事项。

    什么是 Condition 接口?

    在 Java 多线程编程中,通常使用 wait()notify() 方法来实现线程之间的等待和唤醒操作。但这两个方法有一些局限性,例如,只能在 synchronized 块内调用,而且每个对象只有一个等待队列。Condition 接口的引入弥补了这些不足,它提供了更灵活的线程协作方式。

    Condition 接口是 Java 核心库中 java.util.concurrent.locks 包下的一部分,它通常与 ReentrantLock 一起使用。ReentrantLock 是一种可重入锁,与传统的 synchronized 关键字相比,提供了更多的控制和功能。通过 Condition 接口,你可以为每个 ReentrantLock 创建多个条件(Condition),每个条件可以控制一组线程的等待和唤醒。

    Condition 接口的主要方法

    Condition 接口定义了一些重要的方法,用于线程的等待和唤醒:

    • await():使当前线程等待,并释放锁,直到其他线程调用相同条件上的 signal()signalAll() 方法来唤醒它。
    • awaitUninterruptibly():与 await() 类似,但不响应中断。
    • signal():唤醒一个在该条件上等待的线程。如果有多个线程在等待,只会唤醒其中一个,具体唤醒哪个线程不确定。
    • signalAll():唤醒所有在该条件上等待的线程。

    Condition 的基本用法

    创建 Condition

    要使用 Condition 接口,首先需要创建一个与 ReentrantLock 关联的条件对象。通常,一个 ReentrantLock 对象可以创建多个条件对象,用于不同的线程协作。

    ReentrantLock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    
    • 1
    • 2

    等待和唤醒线程

    在使用 Condition 进行线程协作时,通常遵循以下模式:

    等待线程
    lock.lock(); // 获取锁
    try {
        while (条件不满足) {
            condition.await(); // 释放锁,并等待条件满足
        }
        // 执行线程任务
    } finally {
        lock.unlock(); // 释放锁
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    唤醒线程
    lock.lock(); // 获取锁
    try {
        // 修改条件,使等待线程可以继续执行
        condition.signal(); // 唤醒一个等待线程
        // 或者使用 condition.signalAll() 唤醒所有等待线程
    } finally {
        lock.unlock(); // 释放锁
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    示例:生产者和消费者问题

    让我们通过一个简单的生产者和消费者问题来演示 Condition 的使用。在这个问题中,有一个有界缓冲区,生产者线程将数据放入缓冲区,而消费者线程将数据从缓冲区取出。

    首先,我们创建一个有界缓冲区的类:

    import java.util.LinkedList;
    import java.util.Queue;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class BoundedBuffer<T> {
        private Queue<T> buffer = new LinkedList<>();
        private int capacity;
        private Lock lock = new ReentrantLock();
        private Condition notFull = lock.newCondition();
        private Condition notEmpty = lock.newCondition();
    
        public BoundedBuffer(int capacity) {
            this.capacity = capacity;
        }
    
        public void put(T item) throws InterruptedException {
            lock.lock();
            try {
                while (buffer.size() == capacity) {
                    notFull.await();
                }
                buffer.offer(item);
                notEmpty.signal();
            } finally {
                lock.unlock();
            }
        }
    
        public T take() throws InterruptedException {
            lock.lock();
            try {
                while (buffer.isEmpty()) {
                    notEmpty.await();
                }
                T item = buffer.poll();
                notFull.signal();
                return item;
            } finally {
                lock.unlock();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    在这个示例中,我们使用了 ReentrantLock 来保护缓冲区的操作,并分别创建了两个条件 notFullnotEmpty,用于控制缓冲区的状态。

    接下来,我们可以创建生产者和消费者线程,它们分别向缓冲区放入数据和取出数据:

    public class ProducerConsumerExample {
        public static void main(String[] args) {
            BoundedBuffer<Integer> buffer = new BoundedBuffer<>(10);
    
            Thread producerThread = new Thread(() -> {
                try {
                    for (int i = 0; i < 100; i++) {
                        buffer.put(i);
                        System.out.println("Produced: " + i);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
    
            Thread consumerThread = new Thread(() -> {
                try {
                    for (int i = 0; i < 100; i++) {
                        int item = buffer.take();
                        System.out.println("Consumed: " + item);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
    
            producerThread.start();
            consumerThread.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    在这个示例中,生产者线程不断地向缓冲区放入数据,而消费者线程不断地从缓冲区取出数据,它们通过 await()signal() 方法进行线程协作。

    注意事项

    在使用 Condition 接口时,需要注意以下几点:

    1. 必须在获取锁之后才能调用 await()signal()signalAll() 方法,否则会抛出 IllegalMonitorStateException 异常。

    2. 调用 await() 方法后,当前线程将释放锁,允许其他线程获取锁并执行。当线程被唤醒后,它将重新尝试获取锁,然后从 await() 方法返回。

    3. signal() 方法只能唤醒一个等待线程,如果有多个线程在等待,具体唤醒哪一个是不确定的。如果需要唤醒所有等待线程,可以使用 signalAll() 方法。

    4. 在等待时,通常需要将 await() 方法包装在一个循环中,以防止虚假唤醒。

    5. 使用 Condition 接口时,要特别小心死锁和竞态条件等多线程问题,确保线程协作的正确性和安全性。

    总结

    Condition 接口提供了一种更灵活和高级的线程协作机制,可以用于实现复杂的线程同步和通信。通过创建多个条件对象,你可以更精细地控制线程的等待和唤醒。但在使用时需要小心处理锁和条件的关系,以确保线程协作的正确性和可靠性。希望本文对你理解和应用 Condition 接口有所帮助,提高多线程编程的技能。

  • 相关阅读:
    三极管共射放大电路静态工作点怎么设计
    [官方培训]13-渲染输出理解及最佳实践 戴浩军 Epic 笔记
    北大硕士7年嵌入式学习经验分享
    linux权限
    分布式机器学习:异步SGD和Hogwild!算法(Pytorch)
    Erasure-Code-擦除码-2-实现篇
    猿创征文|实例展示Sklearn中三种常用的编码方式OneHotEncoder,LabelEncoder,OrdinalEncoder的使用过程
    Flutter TapGestureRecognizer 如何工作
    pdf密码怎么解除?几种破解方法来看看
    扩散模型浅析
  • 原文地址:https://blog.csdn.net/qq_21484461/article/details/133100244