Java 实例 – 线程挂起(超详细)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

在多线程编程中,线程的挂起(Suspension)是一个核心概念,它决定了程序如何高效地协调多个任务的执行。对于 Java 开发者而言,理解线程挂起的机制和实现方法,能够帮助我们更好地管理资源、避免死锁,并提升程序的健壮性。本文将以“Java 实例 – 线程挂起”为主题,通过案例和代码示例,深入浅出地讲解线程挂起的实现方式、应用场景及常见问题。无论是编程初学者还是中级开发者,都能在本文中找到适合自己的知识切入点。


一、线程挂起的定义与核心场景

1.1 什么是线程挂起?

线程挂起是指暂停线程的执行状态,使其暂时退出运行队列,等待特定条件满足后重新恢复执行。这一操作类似于交通灯控制车辆通行:线程就像道路上的车辆,挂起时相当于在红灯前停车,等待绿灯亮起后继续行驶。

1.2 线程挂起的常见场景

  • 资源竞争:当多个线程竞争共享资源时,需要通过挂起机制避免冲突。
  • 任务协调:例如主线程需要等待子线程完成后再继续执行。
  • 定时任务:线程需要在特定时间点暂停或重新启动。

二、Java 中实现线程挂起的核心方法

Java 提供了多种线程挂起的方法,每种方法的适用场景和底层机制都有所不同。以下将逐一介绍 sleep()wait()join(),并通过代码示例说明其用法。

2.1 方法一:Thread.sleep()

2.1.1 基本语法与作用

Thread.sleep() 是最常用的线程挂起方法之一,它会使当前线程暂停执行指定的毫秒数。需要注意的是,此方法属于静态方法,需通过 Thread.sleep() 调用,而非通过线程对象调用。

public class SleepExample {  
    public static void main(String[] args) {  
        Thread thread = new Thread(() -> {  
            System.out.println("线程开始执行");  
            try {  
                Thread.sleep(2000); // 挂起 2 秒  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            System.out.println("线程恢复执行");  
        });  
        thread.start();  
    }  
}  

2.1.2 关键特性

  • 不释放锁sleep() 不会释放当前线程持有的对象锁。
  • 可中断:若其他线程调用 interrupt(),可提前终止挂起状态。
  • 精确度有限:实际挂起时间可能略长于指定时间,受系统调度影响。

2.2 方法二:Object.wait()

2.2.1 基本语法与作用

Object.wait() 需要在同步块(synchronized)中调用,用于将线程挂起,直到其他线程调用 notify()notifyAll() 唤醒。

public class WaitNotifyExample {  
    private static final Object LOCK = new Object();  
    private static boolean condition = false;  

    public static void main(String[] args) throws InterruptedException {  
        Thread thread = new Thread(() -> {  
            synchronized (LOCK) {  
                while (!condition) {  
                    try {  
                        System.out.println("线程进入等待状态");  
                        LOCK.wait(); // 挂起,直到被唤醒  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                }  
                System.out.println("线程被唤醒");  
            }  
        });  
        thread.start();  

        Thread.sleep(1000); // 主线程等待子线程进入等待状态  
        synchronized (LOCK) {  
            condition = true;  
            LOCK.notify(); // 唤醒一个等待线程  
        }  
    }  
}  

2.2.2 关键特性

  • 释放锁:调用 wait() 会释放当前持有的对象锁。
  • 需配合 notify()/notifyAll():必须由其他线程主动唤醒才能恢复执行。
  • 避免死锁:若未正确设置唤醒条件,可能导致线程永久挂起。

2.3 方法三:Thread.join()

2.3.1 基本语法与作用

join() 方法用于让当前线程等待另一个线程完成后再继续执行。例如,主线程需要等待子线程结束后才能输出最终结果。

public class JoinExample {  
    public static void main(String[] args) {  
        Thread thread = new Thread(() -> {  
            for (int i = 0; i < 3; i++) {  
                System.out.println("子线程执行中...");  
                try {  
                    Thread.sleep(500);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        });  
        thread.start();  

        try {  
            thread.join(); // 主线程等待子线程完成  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("主线程继续执行");  
    }  
}  

2.3.2 关键特性

  • 阻塞当前线程:调用 join() 的线程会被挂起,直到目标线程执行完毕。
  • 可指定超时时间:通过 join(long millis) 可设置最大等待时间。
  • 无需显式唤醒:线程自然执行结束即可自动恢复当前线程。

三、方法对比与选择指南

为了帮助开发者快速选择合适的方法,以下通过表格总结三者的区别:

方法是否释放锁是否需要唤醒是否阻塞当前线程典型场景
Thread.sleep()不释放定时任务、短暂停顿
Object.wait()释放notify()条件等待、生产者-消费者
Thread.join()释放目标线程结束等待线程完成

3.1 选择方法的依据

  • 需要暂停固定时间 → 优先使用 sleep()
  • 依赖其他线程的条件变化 → 使用 wait()notify()
  • 需等待其他线程执行完毕 → 直接调用 join()

四、实战案例:线程挂起的综合应用

4.1 案例背景

假设我们需要实现一个简单的“生产者-消费者”模型:生产者线程将数据放入队列,消费者线程从队列中取出数据处理。当队列为空时,消费者需挂起等待;当队列满时,生产者需挂起等待。

4.2 代码实现

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

public class ProducerConsumerExample {  
    private static final int CAPACITY = 3;  
    private static final Queue<Integer> buffer = new LinkedList<>();  

    public static void main(String[] args) {  
        Thread producer = new Thread(() -> {  
            int count = 0;  
            while (count < 10) {  
                synchronized (buffer) {  
                    while (buffer.size() == CAPACITY) {  
                        try {  
                            System.out.println("队列已满,生产者进入等待");  
                            buffer.wait(); // 挂起生产者  
                        } catch (InterruptedException e) {  
                            e.printStackTrace();  
                        }  
                    }  
                    buffer.add(count++);  
                    System.out.println("生产者生产数据: " + count);  
                    buffer.notify(); // 唤醒消费者  
                }  
            }  
        });  

        Thread consumer = new Thread(() -> {  
            while (true) {  
                synchronized (buffer) {  
                    while (buffer.isEmpty()) {  
                        try {  
                            System.out.println("队列为空,消费者进入等待");  
                            buffer.wait(); // 挂起消费者  
                        } catch (InterruptedException e) {  
                            e.printStackTrace();  
                        }  
                    }  
                    int data = buffer.poll();  
                    System.out.println("消费者消费数据: " + data);  
                    buffer.notify(); // 唤醒生产者  
                }  
            }  
        });  

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

4.3 代码解析

  • 同步块:通过 synchronized 确保对共享资源 buffer 的互斥访问。
  • wait()notify()
    • 生产者在队列满时挂起,消费者在队列空时挂起。
    • 操作后通过 notify() 唤醒等待的线程。
  • 循环检查条件:避免“虚假唤醒”问题,确保线程在条件满足时才恢复执行。

五、常见问题与最佳实践

5.1 常见错误与解决方案

  • 错误1:未在同步块中调用 wait()/notify()

    • 后果:抛出 IllegalMonitorStateException 异常。
    • 解决:确保方法在 synchronized 块或方法中调用。
  • 错误2:忽略 InterruptedException

    • 后果:线程可能无法正常响应中断信号。
    • 解决:在 catch 块中处理异常,或重新抛出中断信号。

5.2 最佳实践

  • 避免无限挂起:在 wait() 时设置超时时间(如 wait(long timeout))。
  • 合理选择方法:根据场景选择 sleep()wait()join(),避免误用。
  • 简化代码逻辑:使用 LockCondition 类替代传统 synchronized,提升灵活性。

六、结论

通过本文对“Java 实例 – 线程挂起”的系统性讲解,我们掌握了三种核心方法的实现原理、代码示例及实际应用场景。无论是基础的 sleep() 还是复杂的 wait()/notify() 模式,线程挂起机制始终围绕“协调任务执行”这一核心目标展开。

对于开发者而言,理解线程挂起的本质,结合具体场景选择合适的方法,能够显著提升多线程程序的健壮性和效率。建议通过实际编码练习加深对上述知识点的理解,并在项目中逐步应用这些技巧,以解决真实世界的并发问题。


关键词布局说明:本文通过标题、章节小标题及案例描述,自然融入了“Java 实例 – 线程挂起”这一主题,确保内容既符合 SEO 要求,又保持了技术文档的专业性和可读性。

最新发布