Java进阶篇--Condition与等待通知机制

目录

Condition简介

Condition实现原理

代码示例

await与signal/signalAll的结合思考

代码示例


Condition简介

Condition是Java并发包中的一种机制,用于线程之间的协作和通信。它与锁(Lock)紧密配合使用,并提供了更高级别的等待/通知功能。

下面是Condition的一些特性和区别:

1. 精确唤醒:Condition可以实现精确的线程唤醒机制。使用Object类的wait()方法时,只能通过notify()或notifyAll()方法唤醒等待的线程,无法指定具体哪个线程被唤醒。而使用Condition的signal()方法可以精确地唤醒一个等待线程,或者使用signalAll()方法唤醒所有等待线程。

2. 多条件支持:Condition可以支持多个条件队列。一个Lock对象可以关联多个Condition对象,每个Condition对象都可以管理一个独立的等待队列。线程可以选择在特定的条件下等待和唤醒,从而更加灵活地实现线程间的协作。

3. 等待超时:使用Condition时,可以指定线程等待的时间。调用await()方法时可以传入超时时间,在超过指定时间后线程会自动被唤醒,无需显式地调用notify()或notifyAll()方法。

4. 可中断等待:Condition支持线程中断。调用await()方法后,如果线程被中断,会立即抛出InterruptedException异常,可以在异常处理中进行相应的操作。

5. 可以替代synchronized关键字:使用Condition和Lock可以替代传统的使用synchronized关键字实现线程间通信的方式,更加灵活和可控。Condition的功能更强大,提供了更多的等待/通知机制。

Java进阶篇--Condition与等待通知机制_第1张图片

总的来说,Condition接口提供了更高级别、更强大的线程等待和通知机制。它与Lock接口配合使用,能够满足更复杂的线程间通信需求,具有更高的可控性和扩展性。相比之下,Object类的wait/notify方法更低级,功能相对有限。

Condition实现原理

Condition是Java并发包中的一个重要组件,用于实现基于锁的等待/通知机制。Condition的实现原理分析主要涉及等待队列、await()方法和signal/signalAll()方法三个方面。

1. 等待队列

Condition依赖于底层的锁机制来实现线程同步和互斥,而等待队列则是Condition用于管理等待线程的一种数据结构。在Lock对象中维护了一个等待队列来管理所有等待在该锁上的线程。

等待队列是一个FIFO(先进先出)的队列,其中每个节点代表了一个等待线程。当一个线程调用Condition的await()方法时,它会释放持有的锁并进入等待状态,同时将当前线程插入到等待队列的末尾。而当其他线程调用Condition的signal()或signalAll()方法时,会从等待队列的头部取出一个节点,并将其转移到锁的同步队列中。

2. await()方法实现原理

await()方法是Condition中最核心的方法之一,它用于将当前线程设置为等待状态,并加入到等待队列中。await()方法的具体实现流程如下:

(1) 获得Lock对象。

(2) 将线程状态设置为WAITING。

(3) 将当前线程加入到等待队列的尾部。

(4) 释放锁。

(5) 等待被唤醒。

当await()方法被执行后,线程会释放持有的锁,并进入等待状态。同时,当前线程会插入到Lock对象的等待队列中,等待其他线程调用signal()或signalAll()方法唤醒它。

3. signal/signalAll()方法实现原理

signal()和signalAll()方法是Condition中用于唤醒等待线程的方法。当锁的状态发生变化后,需要唤醒一个或多个等待线程来执行特定的操作,就可以调用这些方法。signal()方法通常只唤醒一个等待线程,而signalAll()方法则会唤醒所有等待线程。

signal/signalAll()方法的具体实现流程如下:

(1) 获得Lock对象。

(2) 从等待队列中取出头部节点。

(3) 将头部节点转移到Lock对象的同步队列中。

(4) 唤醒被转移节点的线程。

(5) 释放锁。

在执行signal/signalAll()方法时,需要获取Lock对象并从等待队列中取出头部节点。然后将头部节点转移到同步队列中,并唤醒该节点对应的线程。最后释放锁,使得其他线程可以继续竞争锁。

总的来说,Condition的实现依赖于底层的锁机制和等待队列,通过等待队列实现了线程的等待和唤醒,通过锁机制实现了线程同步和互斥。当一个线程调用await()方法时,会释放锁并进入等待状态,并且将当前线程加入到等待队列中。而当其他线程调用signal()或signalAll()方法时,会从等待队列中取出一个节点,并将其转移到同步队列中,并唤醒对应的线程。这样就可以实现线程之间的协作和通信,提供更高级别、更灵活的等待/通知机制。

代码示例

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 main {
    private final Lock lock = new ReentrantLock(); // 创建锁对象
    private final Condition notFull = lock.newCondition(); // 非满条件
    private final Condition notEmpty = lock.newCondition(); // 非空条件
    private final Queue buffer = new LinkedList<>(); // 缓冲区
    private final int capacity = 5; // 缓冲区容量

    public void produce() throws InterruptedException {
        while (true) {
            lock.lock(); // 获取锁
            try {
                while (buffer.size() == capacity) { // 如果缓冲区满了,等待非满条件
                    notFull.await();
                }
                int item = produceItem(); // 生产物品
                buffer.offer(item); // 放入缓冲区
                System.out.println("生产: " + item);
                notEmpty.signal(); // 唤醒等待非空条件的消费者线程
            } finally {
                lock.unlock(); // 释放锁
            }
            Thread.sleep(1000); // 生产物品的时间间隔
        }
    }

    public void consume() throws InterruptedException {
        while (true) {
            lock.lock(); // 获取锁
            try {
                while (buffer.isEmpty()) { // 如果缓冲区空了,等待非空条件
                    notEmpty.await();
                }
                int item = buffer.poll(); // 从缓冲区取出物品
                System.out.println("已消耗: " + item);
                notFull.signal(); // 唤醒等待非满条件的生产者线程
            } finally {
                lock.unlock(); // 释放锁
            }
            Thread.sleep(1000); // 消费物品的时间间隔
        }
    }

    private int produceItem() {
        return (int) (Math.random() * 100); // 生产一个随机数作为物品
    }

    public static void main(String[] args) {
        main producerConsumer = new main();
        new Thread(() -> {
            try {
                producerConsumer.produce();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                producerConsumer.consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

await与signal/signalAll的结合思考

await和signal/signalAll是Condition接口定义的方法,用于线程之间的协作和通信。下面是一些思考:

1、await方法的作用是什么?

await方法用于使当前线程等待,直到某个条件为真。当线程调用await方法后,它会释放锁,并进入等待状态,直到其他线程通过signal或signalAll方法将其唤醒。

2、signal方法的作用是什么?

signal方法用于唤醒一个等待中的线程。当某个条件发生变化时,可以调用signal方法来选择唤醒其中一个等待的线程。被唤醒的线程会尝试重新获得锁,并继续执行。

3、signalAll方法的作用是什么?

signalAll方法用于唤醒所有等待中的线程。当某个条件发生变化时,可以调用signalAll方法来唤醒所有等待的线程。被唤醒的线程会尝试重新获得锁,并继续执行。

4、为什么需要使用await和signal/signalAll来实现线程协作和通信?

  • 使用锁和条件的方式可以实现更加精细的线程协作和通信。通过await方法,线程可以主动释放锁,并等待某个条件的发生;
  • 而通过signal/signalAll方法,线程可以选择性地唤醒等待的线程,从而实现更加灵活的线程调度和通信。

5、怎么选择使用signal还是signalAll方法?

  • 如果只有一个线程在等待某个条件,那么使用signal方法来唤醒等待的线程即可。
  • 如果有多个线程在等待某个条件,而且需要同时唤醒它们,那么使用signalAll方法来唤醒所有等待的线程。

6、使用await和signal/signalAll方法需要注意什么?

  • 在使用await方法前,必须先获得锁。否则会抛出IllegalMonitorStateException异常。
  • await方法被调用后,当前线程会释放锁,并进入等待状态,直到其他线程调用signal或signalAll方法来唤醒它。
  • 唤醒等待的线程后,被唤醒的线程会尝试重新获得锁,但不一定立即成功,需要竞争锁资源,可能会有其他线程先获取到锁并执行。
  • 在使用signal/signalAll方法时,应该确保在调用这些方法之前已经改变了相应的条件,否则可能导致等待的线程无法满足条件而继续等待。

这种等待/通知机制的典型应用场景是生产者与消费者问题。生产者线程通过获取锁并进入等待状态,直到有消费者线程进行通知唤醒。消费者线程在消费完数据后,再次获取锁并通知等待中的生产者线程。这样可以实现生产者与消费者之间的协同工作,避免了无效的轮询和资源浪费。

需要注意的是,在使用await和signal/signalAll时,应该确保在调用这些方法之前已经改变了相应的条件,这样等待的线程才能满足条件而被唤醒。

总的来说,await和signal/signalAll是一种强大的线程协作和通信机制,在多线程编程中可以帮助我们实现高效的线程调度和通信模式。合理使用这些方法可以避免死锁、提高程序的性能和可靠性。

代码示例

以下是一个简单的示例代码,演示了如何使用await和signal方法实现线程之间的等待和通知机制,以解决生产者与消费者问题。

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 main {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private final Queue queue = new LinkedList<>();
    private final int MAX_SIZE = 10;

    public void produce() throws InterruptedException {
        lock.lock(); // 获取锁
        try {
            while (queue.size() == MAX_SIZE) {
                // 队列已满,生产者进入等待状态
                notFull.await();
            }
            // 生产数据
            int number = getNextNumber();
            queue.offer(number);
            System.out.println("生产: " + number);

            // 唤醒一个等待中的消费者线程
            notEmpty.signal();
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    public void consume() throws InterruptedException {
        lock.lock(); // 获取锁
        try {
            while (queue.isEmpty()) {
                // 队列为空,消费者进入等待状态
                notEmpty.await();
            }
            // 消费数据
            int number = queue.poll();
            System.out.println("已消耗: " + number);

            // 唤醒一个等待中的生产者线程
            notFull.signal();
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    private int getNextNumber() {
        // 模拟生成数据的过程
        return (int) (Math.random() * 100);
    }

    public static void main(String[] args) {
        main example = new main();

        // 创建生产者线程
        Thread producerThread = new Thread(() -> {
            try {
                while (true) {
                    example.produce();
                    Thread.sleep(1000); // 生产一个数据后休眠1秒
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 创建消费者线程
        Thread consumerThread = new Thread(() -> {
            try {
                while (true) {
                    example.consume();
                    Thread.sleep(2000); // 消费一个数据后休眠2秒
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 启动线程
        producerThread.start();
        consumerThread.start();
    }
}

在这个例子中,我们使用了Lock和Condition对象来实现等待/通知机制。生产者线程通过调用notFull.await()方法进入等待状态,直到队列不满时被唤醒;消费者线程通过调用notEmpty.await()方法进入等待状态,直到队列非空时被唤醒。

注意,在生产者生产数据后,需要调用notEmpty.signal()方法来唤醒一个等待中的消费者线程;在消费者消费数据后,需要调用notFull.signal()方法来唤醒一个等待中的生产者线程。这样就实现了生产者与消费者的协作工作。

你可能感兴趣的:(Java进阶篇,java,开发语言)