并发 - 线程核心协调方法

并发编程 —— 线程核心协调方法

Thread 类和 Object 类提供了一些核心方法,用于控制和协调线程的执行状态和交互。理解它们是编写健壮多线程代码的基础。

1. sleep() 方法 (Thread 类)

  • 作用:使当前正在执行的线程暂停一段时间,让出 CPU 的执行权。

  • 方法签名:

    • public static native void sleep(long millis) throws InterruptedException
    • public static void sleep(long millis, int nanos) throws InterruptedException
  • 特点:

    1. 释放 CPU:线程会从 RUNNING 状态进入 TIMED_WAITING (定时等待) 状态,让出 CPU 给其他线程使用。
    2. 不释放锁:如果当前线程持有任何对象的监视器锁(synchronized 锁),调用 sleep() 方法不会释放这些锁。其他线程仍然无法进入被该锁保护的同步代码块。
    3. 可能被中断:sleep() 方法会抛出 InterruptedException(线程中断异常)。这意味着在线程睡眠期间,如果被其他线程中断 (interrupt()),它会立即醒来并抛出此异常。
  • 使用场景:

    • 模拟耗时操作,以便观察并发行为。
    • 控制线程执行频率,如定时任务。
    • 在不满足某个条件时,进行短暂的等待重试。
  • 生活比喻:就像你正在工作(线程执行),突然决定小睡一会儿。你放下手头的工作(让出 CPU),但你依然坐在自己的座位上,没有把办公室的钥匙(持有的锁)交给任何人。如果有人叫醒你(中断),你会醒来。

public class SleepDemo {
    public static void main(String[] args) {
        Runnable task = () -> {
            System.out.println(Thread.currentThread().getName() + " 开始执行...");
            try {
                Thread.sleep(2000); // 睡眠 2 秒
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " 被中断了!");
                Thread.currentThread().interrupt(); // 重新设置中断标志
            }
            System.out.println(Thread.currentThread().getName() + " 结束执行。");
        };

        Thread t = new Thread(task, "睡眠线程");
        t.start();

        try {
            Thread.sleep(500); // 主线程睡眠 0.5 秒
            t.interrupt();     // 尝试中断睡眠线程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2. yield() 方法 (Thread 类)

  • 作用:暗示(提示)线程调度器:当前线程愿意放弃当前的 CPU 执行权,让给其他具有相同优先级的线程执行。

  • 方法签名:public static native void yield()

  • 特点:

    1. 释放 CPU:线程从 RUNNING 状态转变为 RUNNABLE (就绪) 状态,等待 CPU 再次调度。
    2. 不释放锁:和 sleep() 一样,yield() 方法不会释放当前线程持有的任何对象锁。
    3. 调度器可能忽略:yield() 只是一个“提示”,线程调度器不一定会采纳这个建议。即使调用了 yield(),当前线程也可能立即重新获得 CPU 执行权。
    4. 无参数:没有时间参数,无法指定放弃多久。
  • 使用场景:

    • 当一个线程执行一个耗时但非关键的操作时,可以适时调用 yield(),给其他线程机会。
    • 在调试或测试时,用于增加线程切换的频率,暴露潜在的并发问题。
    • 通常在生产环境中很少直接使用,因为它高度依赖于操作系统的调度策略。
  • 生活比喻:就像你正在排队买东西(线程执行),你对后面的人说:“你先请”(yield())。但后面的人可能礼貌性地让你先,或者你旁边的另一个人直接插队了,也可能你又重新站回了最前面。调度器就像这个排队系统,不一定听你的。

public class YieldDemo {
    public static void main(String[] args) {
        Runnable task1 = () -> {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
                if (i == 2) {
                    Thread.yield(); // 线程在 i=2 时让出 CPU
                }
            }
        };

        Thread t1 = new Thread(task1, "线程A");
        Thread t2 = new Thread(task1, "线程B"); // 注意:t1和t2运行的是同一个Runnable实例,但它们是不同的线程

        t1.start();
        t2.start();
    }
}

3. join() 方法 (Thread 类)

  • 作用:等待某个线程执行完成。当一个线程调用另一个线程的 join() 方法时,调用线程会被阻塞,直到被 join 的线程执行结束或达到指定的等待时间。

  • 方法签名:

    • public final void join() throws InterruptedException
    • public final synchronized void join(long millis) throws InterruptedException
    • public final synchronized void join(long millis, int nanos) throws InterruptedException
  • 特点:

    1. 阻塞调用线程:调用 join() 的线程会进入 WAITING (等待) 或 TIMED_WAITING (定时等待) 状态,直到被 join 的线程死亡。
    2. 不释放锁:join() 方法同样不会释放当前线程持有的任何对象锁。
    3. 可被中断:join() 方法会抛出 InterruptedException,表示等待期间线程被中断。
  • 使用场景:

    • 确保某个线程的任务在当前线程之前完成。例如,主线程需要等待所有子线程计算完毕后,才能汇总结果。
    • 控制线程的执行顺序,建立线程间的依赖关系。
  • 生活比喻:就像你和你的朋友小明(另一个线程)约好了一起去看电影。你准备好了,但小明还在忙。于是你对小明说:“你忙完我再走”(小明线程.join())。你就会坐在沙发上(被阻塞),等着小明忙完。

public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread workerThread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 开始执行工作...");
            try {
                Thread.sleep(3000); // 模拟耗时工作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 工作完成。");
        }, "WorkerThread");

        workerThread.start();
        System.out.println(Thread.currentThread().getName() + " 等待 WorkerThread 完成...");

        workerThread.join(); // 主线程等待 workerThread 执行完毕
        // workerThread.join(1000); // 主线程最多等待 1 秒

        System.out.println(Thread.currentThread().getName() + " 继续执行后续任务。");
    }
}

4. interrupt() 方法 (Thread 类)

  • 作用:请求中断一个线程,而非强制停止。它是一种协作式中断机制。

  • 方法签名:

    • public void interrupt()
    • public boolean isInterrupted()
    • public static boolean interrupted()
  • 特点:

    1. 设置中断标志位:调用 thread.interrupt() 方法只会将目标线程的中断标志位 (interrupted status flag) 设置为 true。它本身不会立即停止线程。
    2. 响应中断:
      • 如果线程当前正处于阻塞状态(如 sleep(), join(), wait(), io 阻塞),那么它会立即抛出 InterruptedException,并且清除中断标志位(变为 false)。
      • 如果线程当前没有被阻塞,中断标志位会变为 true。线程需要自己主动检查 isInterrupted() 或 Thread.interrupted() 来判断是否被中断,并决定如何响应。
    3. 不释放锁:interrupt() 机制与线程是否持有锁无关。
  • 使用场景:

    • 优雅地停止或取消一个长时间运行的任务。
    • 从阻塞状态中唤醒一个线程。
  • 生活比喻:就像你在专心工作(线程执行),你的同事轻轻地拍了拍你的肩膀(interrupt())。

    • 如果你正在午休(sleep() 阻塞),你会惊醒过来(抛出异常),然后问“怎么了?”(处理中断)。
    • 如果你没在午休,你只是感觉到有人拍你(中断标志位为 true),但你可能选择继续工作,或者停下来问“有什么事?”(主动检查中断标志并响应)。
public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread interruptibleThread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) { // 检查中断标志
                System.out.println(Thread.currentThread().getName() + " 正在工作...");
                try {
                    Thread.sleep(1000); // 模拟耗时操作,此处会响应中断
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() + " 在 sleep 期间被中断,准备退出。");
                    // 收到 InterruptedException 后,中断标志会被清除(变为 false)
                    // 重新设置中断标志,以便外层能感知到线程被中断
                    Thread.currentThread().interrupt(); 
                }
            }
            System.out.println(Thread.currentThread().getName() + " 循环结束,线程终止。中断状态: " + Thread.currentThread().isInterrupted());
        }, "InterruptibleThread");

        interruptibleThread.start();
        Thread.sleep(2500); // 主线程等待一段时间
        System.out.println(Thread.currentThread().getName() + " 请求中断 InterruptibleThread...");
        interruptibleThread.interrupt(); // 请求中断子线程
        interruptibleThread.join(); // 等待子线程终止
        System.out.println(Thread.currentThread().getName() + " 结束。");
    }
}

5. Object 类的协调方法 (wait / notify / notifyAll)

与前面介绍的方法不同,wait(), notify(), notifyAll() 不是 Thread 类的方法,而是 Object 类的方法。这意味着任何 Java 对象都可以调用它们。这些方法是 Java 中实现线程间协作与通信 (Inter-thread communication) 的基石,它们必须与 synchronized 关键字配合使用。

  • 前提条件:要调用一个对象的 wait(), notify() 或 notifyAll() 方法,当前线程必须持有该对象的监视器锁(即必须在 synchronized(obj) 代码块中),否则会抛出 IllegalMonitorStateException。

5.1 wait() 方法

  • 作用:使当前线程进入等待状态,并立即释放当前持有的该对象的锁。线程会一直等待,直到其他线程调用该对象的 notify() 或 notifyAll() 方法。

  • 方法签名:

    • public final void wait() throws InterruptedException
    • public final void wait(long timeout) throws InterruptedException
  • 特点:

    1. 释放锁:这是 wait() 与 sleep() 最本质的区别。释放锁使得其他线程有机会获得该锁并执行同步代码。
    2. 进入等待状态:线程会从 RUNNABLE 状态进入 WAITING 或 TIMED_WAITING 状态。
    3. 可被中断:等待期间可以被中断,并抛出 InterruptedException。
    4. 虚假唤醒 (Spurious Wakeup):线程有可能在没有被 notify() 的情况下被唤醒。因此,官方推荐总是在一个循环中调用 wait(),并在唤醒后再次检查等待的条件是否满足。
  • 生活比喻:就像你(线程)发现某个条件不满足(比如共享队列是空的),你不能继续工作。于是你交出了办公室的钥匙(释放锁),然后去休息室(WAITING 状态)等着。你不会自己醒来,必须等别人来通知你。

5.2 notify() 和 notifyAll() 方法

  • 作用:

    • notify(): 唤醒一个正在等待该对象锁的线程。具体唤醒哪个线程是不确定的,取决于 JVM 的实现。
    • notifyAll(): 唤醒所有正在等待该对象锁的线程。
  • 特点:

    1. 不立即释放锁:调用 notify() 或 notifyAll() 的线程并不会立即释放锁。它会继续执行 synchronized 代码块中余下的代码,直到同步块执行完毕后才会释放锁。
    2. 竞争锁:被唤醒的线程并不会立即执行,而是会进入 RUNNABLE 状态,并重新参与到该对象锁的竞争中。只有当它再次获得锁后,才能从 wait() 方法处继续执行。
  • 生活比喻:另一个同事(线程)完成了某项工作(比如向共享队列中放入了元素),他进入办公室(持有锁),然后去休息室的门口大喊一声:“有活干了!”(notifyAll())。所有在休息室里等待的工人都被唤醒了,然后他们会一窝蜂地冲向办公室门口,试图抢夺唯一的钥匙(竞争锁)。

5.3 代码示例:生产者-消费者模型

import java.util.LinkedList;
import java.util.Queue;

public class ProducerConsumerDemo {
    public static void main(String[] args) {
        Queue<Integer> buffer = new LinkedList<>();
        int maxSize = 5;
        final Object lock = new Object();

        Thread producer = new Thread(() -> {
            int value = 0;
            while (true) {
                synchronized (lock) {
                    // 缓冲区满了,生产者等待
                    while (buffer.size() == maxSize) {
                        try {
                            System.out.println("缓冲区已满,生产者 " + Thread.currentThread().getName() + " 进入等待。");
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("生产者 " + Thread.currentThread().getName() + " 生产了: " + value);
                    buffer.add(value++);
                    // 通知消费者可以消费了
                    lock.notifyAll();
                    try {
                        Thread.sleep(500); // 模拟生产耗时
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "P1");

        Thread consumer = new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    // 缓冲区空了,消费者等待
                    while (buffer.isEmpty()) {
                        try {
                            System.out.println("缓冲区为空,消费者 " + Thread.currentThread().getName() + " 进入等待。");
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    int value = buffer.poll();
                    System.out.println("消费者 " + Thread.currentThread().getName() + " 消费了: " + value);
                    // 通知生产者可以生产了
                    lock.notifyAll();
                     try {
                        Thread.sleep(1000); // 模拟消费耗时
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "C1");

        producer.start();
        consumer.start();
    }
}

6. 总结对比

方法 所属类 作用 是否释放 CPU 是否释放锁 响应中断 线程状态
wait() Object 等待条件满足 是 是 是 (抛出 InterruptedException) WAITING/TIMED_WAITING
notify() Object 唤醒一个等待线程 否 (执行完同步块才放) 否 否 不改变
notifyAll() Object 唤醒所有等待线程 否 (执行完同步块才放) 否 否 不改变
sleep() Thread 暂停当前线程 是 否 是 (抛出 InterruptedException) TIMED_WAITING
yield() Thread 让出 CPU (暗示) 是 否 否 RUNNABLE
join() Thread 等待其他线程终止 是 (调用线程) 否 是 (抛出 InterruptedException) WAITING/TIMED_WAITING
interrupt() Thread 请求中断线程 否 (只设标志) 否 否 (只设标志,阻塞方法抛异常) 不改变线程状态,只修改中断标志
posted @ 2026-01-21 16:03  我是刘瘦瘦  阅读(31)  评论(0)    收藏  举报