JAVA多线程等待唤醒机制

JAVA多线程等待唤醒机制_第1张图片

为什么要处理线程间通信:

当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据。

比如:线程A用来生产包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,此时B线程必须等到A线程完成后才能执行,那么线程A与线程B之间就需要线程通信,即—— 等待唤醒机制。

这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。

等待唤醒机制(Wait-Notify Mechanism)是一种用于多线程环境中的同步策略,主要用于实现线程间的通信和同步。这种机制允许一个线程(或多个线程)在特定条件未满足时暂停其执行(等待状态),而当条件满足时,另一个线程可以通知处于等待状态的线程恢复执行(唤醒操作)。等待唤醒机制是多线程编程中的一个核心概念,有助于保证线程安全和资源的有效利用。

主要组成部分

等待唤醒机制主要包括以下几个部分:

  • Wait(): 当一个线程调用对象的 wait() 方法时,它会释放当前持有的锁,并进入等待状态,直到其他线程通过 notify()notifyAll() 方法来唤醒它。

  • wait(long timeout)

    这种方法会在指定的时间后自动唤醒线程,即使没有调用 notify() 方法。

  • 唤醒 (Notify): 当条件满足时,线程可以通过调用对象的 notify()notifyAll() 方法来通知一个或所有处于等待状态的线程,使它们可以重新获取锁并继续执行。

区分sleep()和wait()

相同点:一旦执行,都会使得当前线程结束执行状态,进入阻塞状态。

不同点:

① 定义方法所属的类:sleep():Thread中定义。 wait():Object中定义

② 使用范围的不同:sleep()可以在任何需要使用的位置被调用; wait():必须使用在同步代码块或同步方法中

③ 都在同步结构中使用的时候,是否释放同步监视器的操作不同:sleep():不会释放同步监视器 ;wait():会释放同步监视器

④ 结束等待的方式不同:sleep():指定时间一到就结束阻塞。 wait():可以指定时间也可以无限等待直到notify或notifyAll。

1. 使用 wait()notify()

这是最基本的等待唤醒机制,它们定义在 Object 类中,因此任何对象都可以作为锁对象使用。

public synchronized void doSomething() {
    while (!someCondition) {
        try {
            this.wait();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            // 处理中断
        }
    }
    // 执行操作
}

public synchronized void changeCondition() {
    someCondition = true;
    this.notify(); // 唤醒一个等待的线程
}
2. 使用 wait(long timeout)

这种方法会在指定的时间后自动唤醒线程,即使没有调用 notify() 方法。

public synchronized void doSomething() {
    long start = System.currentTimeMillis();
    while (!someCondition) {
        try {
            long timeout = someConditionTimeout - (System.currentTimeMillis() - start);
            if (timeout > 0) {
                this.wait(timeout);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            // 处理中断
        }
    }
    // 执行操作
}
3. 使用 Condition 接口

Conditionjava.util.concurrent.locks 包中提供的高级同步工具,可以更灵活地控制线程间的同步。

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

private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private boolean someCondition = false;

public void doSomething() {
    lock.lock();
    try {
        while (!someCondition) {
            condition.await();
        }
        // 执行操作
    } finally {
        lock.unlock();
    }
}

public void changeCondition() {
    lock.lock();
    try {
        someCondition = true;
        condition.signal(); // 唤醒一个等待的线程
    } finally {
        lock.unlock();
    }
}
4. 使用 LockSupport

LockSupport 类提供了一种不同的线程阻塞和唤醒机制,可以更精细地控制线程。

import java.util.concurrent.locks.LockSupport;

public void doSomething() {
    while (!someCondition) {
        LockSupport.park(this); // 阻塞当前线程
    }
    // 执行操作
}

public void changeCondition() {
    someCondition = true;
    LockSupport.unpark(Thread.currentThread()); // 唤醒线程
}

注意事项

  • 锁的作用域wait()notify() 必须在同步代码块或同步方法中调用。
  • 中断响应:线程在等待时可能会被中断,需要适当地处理 InterruptedException
  • 锁对象一致性wait()notify() 必须由同一个锁对象调用。
  • 避免死锁:确保线程按照一致的顺序获取锁,防止死锁发生。
  • 正确使用 notifyAll():如果使用 notifyAll(),所有等待线程都会有机会获得锁,但不一定所有线程都能继续执行,因为锁只有一把。

使用场景

等待唤醒机制通常用于以下场景:

  • 生产者-消费者模型

    :生产者生产数据,消费者消费数据。消费者在没有数据可用时等待,而生产者在数据准备好时唤醒消费者

  • 同步数据访问:在线程之间共享资源时,确保线程只在适当条件下访问数据。

  • 线程间的条件同步:线程A等待某个条件满足,线程B在满足条件后唤醒线程A。

你可能感兴趣的:(JAVA,java)