JAVAEE——多线程(2)

Thread类基本用法

1.线程创建

1:继承java.lang.Thread类

这是最直观的线程创建方式,通过继承 Thread 类并重写其run()方法(线程执行体)来实现。

实现步骤

1.自定义线程类,继承Thread类;
2.重写Thread类的run()方法,编写线程需要执行的业务逻辑;
3.创建自定义线程类的实例;
4.调用实例的start()方法启动线程(注意:不能直接调用 run () 方法,否则只是普通方法调用,不会创建新线程)。

java 复制代码
// 1. 自定义线程类,继承Thread类
class MyThread extends Thread {
    // 2. 重写run()方法,线程执行体
    @Override
    public void run() {
        // 线程要执行的业务逻辑
        for (int i = 0; i < 5; i++) {
            System.out.println("继承Thread类的线程:" + Thread.currentThread().getName() + ",计数:" + i);
            try {
                // 线程休眠100毫秒,模拟耗时操作
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 测试类
public class ThreadCreateDemo1 {
    public static void main(String[] args) {
        // 3. 创建自定义线程实例
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        // 设置线程名称
        thread1.setName("线程1");
        thread2.setName("线程2");
        
        // 4. 调用start()方法启动线程
        thread1.start();
        thread2.start();
        
        // 主线程逻辑
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程:" + Thread.currentThread().getName() + ",计数:" + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2.实现java.lang.Runnable接口

这是实际开发中更推荐的方式,解决了继承Thread类的单继承限制问题,同时便于实现资源共享。

实现步骤

1.自定义任务类,实现Runnable接口;
2.实现接口中的run()方法,编写线程执行的业务逻辑;
3.创建自定义Runnable任务实例;
4.将Runnable任务实例作为参数,传入Thread类的构造器,创建Thread线程实例;
5.调用Thread实例的start()方法启动线程。

java 复制代码
/**
 * 通过Thread真正创建线程,
 * 线程里需要干啥,通过Runnable来表示
 */

/**
 * 目的是让任务本身和线程这个概念能够解耦合,
 * 从而后续如果变更代码,采用Runnable这样的方案,代码的修改会更简单
 */
public class Demo2_runnable {
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.run();
    }

    
    public static class MyRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println("Hello world");
        }
    }

}

3.使用lambda表达式

最为常见的一种写法,与上述第1种创建方法思想一致,只是简化了它的语法

java 复制代码
public class Demo5 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true){
                System.out.println("Hello");
            }
        });
        thread.start();
        while (true){
            System.out.println("AAA");

        }

    }
}

2.线程中断

1.中断的本质核心

Java 线程中断不是强制终止线程,而是给目标线程设置一个「中断标记」(布尔类型),表示 "当前线程收到了中断请求"。线程是否响应这个请求,完全由线程内部的业务逻辑决定(可选择立即退出、执行完当前任务后退出,或忽略中断),这是一种安全的协作式机制。

2.线程中断的核心API

1.interrupt(),实例方法,给目标线程设置**中断标记,**触发中断通知

2.isInterrupted(),实例方法,检查当前线程的中断标记状态

3.interrupted(),静态方法,检查当前线程中断标记状态,同时清除中断标记

java 复制代码
public class ThreadInterruptBlockDemo {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个处于sleep阻塞的线程
        Thread blockThread = new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + ":进入睡眠阻塞,时长2000毫秒");
                // 阻塞状态:sleep()会响应中断,抛出InterruptedException
                Thread.sleep(2000); 
                System.out.println(Thread.currentThread().getName() + ":睡眠正常结束(未被中断)");
            } catch (InterruptedException e) {
                // 捕获异常:此时中断标记已被自动清除(变为false)
                System.out.println(Thread.currentThread().getName() + ":捕获到中断异常,当前中断标记:" + Thread.currentThread().isInterrupted());
                // 可选:重新设置中断标记,让后续逻辑感知到中断
                Thread.currentThread().interrupt();
                System.out.println(Thread.currentThread().getName() + ":重新设置中断标记,当前中断标记:" + Thread.currentThread().isInterrupted());
                // 优雅退出线程(核心:响应中断,终止任务)
                return;
            }
        }, "阻塞线程");

        // 启动线程
        blockThread.start();
        // 主线程休眠500毫秒,确保blockThread进入sleep状态
        Thread.sleep(500);
        // 给blockThread发送中断请求
        System.out.println("主线程:给阻塞线程发送中断请求");
        blockThread.interrupt();

        // 等待blockThread执行完成
        blockThread.join();
        System.out.println("主线程:程序执行结束");
    }
}

3.关键注意事项

1.中断不是强制终止 :线程是否退出完全由自身逻辑决定,避免了强制终止导致的资源未释放(如文件流、数据库连接未关闭)问题;

2.InterruptedException 异常处理 :捕获该异常后,中断标记会被清除,若后续逻辑需要感知中断,需手动调用Thread.currentThread().interrupt()重新设置标记;

3.避免忽略中断 :不要捕获InterruptedException后不做任何处理,否则线程无法响应中断,违背中断机制的设计初衷;

4.静态方法 interrupted () 的慎用:该方法会清除中断标记,若需要保留标记状态,应使用实例方法isInterrupted()。

总结

  1. 线程中断是协作式通知机制,核心是设置中断标记,而非强制终止线程;

  2. 3 个核心 API:interrupt()(设标记)、isInterrupted()(查标记不清除)、interrupted()(查标记并清除);

  3. 阻塞状态(sleep/wait/join):响应中断并抛出InterruptedException,自动清除标记;

  4. 运行状态:需主动检查isInterrupted()标记,自行实现优雅退出逻辑;

  5. 异常处理时,若需后续感知中断,需手动重新设置中断标记。

3.线程等待

线程等待的核心是让一个线程暂停执行(进入阻塞状态),等待特定条件满足(如其他线程执行完毕、资源就绪)后再恢复执行,用于协调多个线程的执行顺序或解决资源竞争问题。

1.核心方法------Thread.join

join() 是 Thread 类的实例方法,核心作用是:让调用 join() 方法的当前线程(如主线程)阻塞,直到被调用 join() 的目标线程(如子线程)执行完成(终止)后,当前线程才会恢复执行。适用于需要按顺序执行线程的场景(如主线程等待子线程计算完成后,获取子线程的执行结果)。

核心特性

支持超时等待:提供 join(long millis)(毫秒级)、join(long millis, int nanos)(纳秒级)重载方法,超过指定时间后,当前线程不再阻塞,直接恢复执行;

无参 join() 等价于 join(0),表示无限等待,直到目标线程终止;

可响应中断:当阻塞线程被调用 interrupt() 时,会抛出 InterruptedException 异常,并自动清除中断标记;

底层依赖 Object.wait() 实现,无需手动获取锁,使用简单。

java 复制代码
public class ThreadJoinDemo {
    public static void main(String[] args) {
        // 目标线程1:模拟耗时任务(计算1到5的累加)
        Thread thread1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ":开始执行耗时任务");
            int sum = 0;
            for (int i = 1; i <= 5; i++) {
                sum += i;
                try {
                    Thread.sleep(200); // 模拟任务耗时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":任务执行完成,累加和为:" + sum);
        }, "子线程1");

        // 目标线程2:模拟简单任务
        Thread thread2 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ":开始执行简单任务");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":简单任务执行完成");
        }, "子线程2");

        // 启动目标线程
        thread1.start();
        thread2.start();

        try {
            System.out.println(主线程:开始等待子线程1执行完毕");
            // 主线程调用thread1.join(),阻塞直到thread1执行完成
            thread1.join(); 
            System.out.println("主线程:子线程1执行完毕,开始等待子线程2(超时1000毫秒)");
            // 主线程等待thread2,最多等待1000毫秒(此处thread2早已执行完成,直接恢复)
            thread2.join(1000); 
        } catch (InterruptedException e) {
            System.out.println("主线程:等待被中断");
            e.printStackTrace();
        }

        System.out.println("主线程:所有子线程(或超时)执行完毕,主线程继续执行");
    }
}

2.核心方法------object.wait()/notify()/notifyAll()

wait() 是 Object 类的实例方法(所有 Java 对象都拥有),核心作用是:让当前线程释放对象锁,进入该对象的等待队列,阻塞等待其他线程唤醒(或超时)

核心特性

  • 必须在同步环境中调用:wait() / notify() / notifyAll() 必须在 synchronized 同步代码块或同步方法中执行(需先获取该对象的监视器锁),否则会抛出 IllegalMonitorStateException 异常;
  • 释放对象锁:线程调用 wait() 后,会立即释放持有的该对象锁(与 Thread.sleep() 不同,sleep() 不会释放锁),让其他线程有机会获取锁;
  • 唤醒后需重新竞争锁:被 notify() / notifyAll() 唤醒的线程,不会立即恢复执行,而是需要重新竞争对象锁,获取锁后才会继续执行 wait() 之后的代码;
  • 支持超时等待:wait(long millis) / wait(long millis, int nanos) 重载方法,超时后线程自动唤醒,无需其他线程通知;
  • 避免虚假唤醒:线程可能会无原因地被唤醒(虚假唤醒),因此必须在 循环中 判断等待条件,而非 if 判断。

关键方法

1.wait(),当前线程释放锁,无限期阻塞,直到其他线程调用该对象的 notify()/notifyAll() 唤醒

2.notify(),唤醒该对象等待队列中的 一个随机线程,使其进入锁竞争队列

3.notifyAll(),唤醒该对象等待队列中的 所有线程,使其进入锁竞争队列

以使用三个线程打印ABC为例

java 复制代码
public static void main(String[] args) throws InterruptedException {
        Object object1 = new Object();
        Object object2 = new Object();
        Object object3 = new Object();
        Thread t1 = new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    synchronized (object1){
                        object1.wait();
                    }
                    Thread.sleep(1000);
                    System.out.print("A");
                    synchronized (object2){
                        object2.notify();
                    }
                }
            }catch (InterruptedException e){
                throw new RuntimeException();
            }
        });
        Thread t2 = new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    synchronized (object2){
                        object2.wait();
                    }
                    Thread.sleep(1000);
                    System.out.print("B");
                    synchronized (object3){
                        object3.notify();
                    }
                }
            }catch (InterruptedException e){
                throw new RuntimeException();
            }
        });
        Thread t3 = new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    synchronized (object3){
                        object3.wait();
                    }
                    Thread.sleep(1000);
                    System.out.print("C");
                    System.out.println();
                    synchronized (object1){
                        object1.notify();
                    }
                }
            }catch (InterruptedException e){
                throw new RuntimeException();
            }
        });
        t1.start();
        t2.start();
        t3.start();
        Thread.sleep(100);
        synchronized (object1){
            object1.notify();
        }

    }

3.注意事项

1.join() 异常处理:调用 join() 时必须捕获 InterruptedException ,避免线程阻塞时无法响应中断;

2.wait() 必在同步环境 :遗漏 synchronized 会抛出 IllegalMonitorStateException,这是高频错误;

3.虚假唤醒处理:wait() 必须配合 while 循环判断等待条件,不能用 if(if 仅判断一次,虚假唤醒后会直接执行后续逻辑,导致错误);

4.wait() 与 sleep() 区别:wait()释放锁 、需同步环境、用于线程协作;sleep() 不释放锁、无调用环境限制、用于简单延时。

4.线程休眠

线程休眠的核心是通过 Thread.sleep() 静态方法实现,用于让当前执行的线程暂停执行指定时长,进入阻塞状态,待休眠时间结束后自动恢复到就绪状态(并非立即执行,需等待 CPU 调度)。

1.核心本质

Thread.sleep() 是 Java 提供的线程延时工具,其核心作用是:强制当前线程放弃 CPU 执行权,进入 "休眠阻塞" 状态,在指定的时间内,该线程不会参与 CPU 的调度竞争。当休眠时间耗尽,线程会自动从阻塞状态切换到就绪状态,等待 CPU 分配执行时间后,才会继续执行后续代码。

2.核心方法------Thread.sleep()

Thread.sleep() 是 java.lang.Thread 类的静态方法,仅作用于当前线程(即调用该方法时所在的线程),提供两种重载形式

1.public static void sleep(long millis) throws InterruptedException,休眠指定毫秒数(millis ≥ 0,若为 0 则等同于让线程放弃当前 CPU 时间片,立即进入就绪态)

2.public static void sleep(long millis, int nanos) throws InterruptedException,休眠毫秒数 + 纳秒数(nanos 范围:0~999999),实际精度受操作系统时钟影响,通常无需使用

关键说明

静态方法特性:不能通过线程实例调用(语法上允许 thread.sleep(),但实际仍作用于当前线程,非目标线程,易误导,不推荐);

检查异常:两种方法均抛出 InterruptedException,必须捕获或声明抛出(线程休眠期间若被中断,会触发该异常并终止休眠)。

核心特性

1.不释放对象监视器锁(关键区别于 Object.wait())

线程调用 sleep() 后,只会放弃 CPU 执行权,不会释放任何已持有的对象锁(synchronized 锁)。这意味着,若线程在同步代码块 / 同步方法中休眠,其他线程将无法获取该对象的锁,只能等待休眠线程唤醒并执行完同步逻辑后,才有机会竞争锁。
2.响应线程中断

线程休眠期间,若其他线程调用该线程的 interrupt() 方法,休眠线程会立即被唤醒,并抛出 InterruptedException 异常,同时自动清除线程的中断标记。

3.休眠时间是 "最小延时",非精确时间

sleep() 指定的时间是线程休眠的最短时间,线程唤醒后不会立即执行,而是进入就绪态等待 CPU 调度。因此,线程实际恢复执行的时间可能会超过指定的休眠时间,具体取决于 CPU 的繁忙程度。
4.不改变线程的中断状态(非异常场景)

若线程未被中断,休眠正常结束后,线程的中断标记状态不会发生变化。

3.注意事项

1.避免错误调用实例方法 :不要通过 new Thread().sleep() 形式调用(语法合法但逻辑错误),该方法仍作用于当前线程,非创建的线程实例;
2.休眠时间非精确值 :由于线程唤醒后需等待 CPU 调度,实际恢复执行的时间可能大于指定的休眠时间;
3.中断异常的处理:休眠期间线程被中断会抛出 InterruptedException,捕获该异常后,中断标记会被自动清除,若后续需感知中断,需手动重新设置;

4.不释放锁的风险:在同步环境中使用 sleep() 时,若休眠时间过长,会导致其他线程长期无法获取锁,影响程序性能;

5.获取线程实例

1.核心方式------Thread.currentThread()-获取当前正在执行的线程实例

Thread.currentThread() 是 Thread 类的静态方法 ,核心作用是:获取当前正在执行这段代码的线程实例(即当前线程自身)。这是最常用的获取线程实例的方式,适用于需要在线程内部获取自身引用、查询线程信息(名称、状态、优先级等)的场景。

核心特性

静态方法:直接通过 Thread.currentThread() 调用,无需依赖线程实例;
实时性:始终返回当前执行代码的线程,无论在主线程还是子线程中调用,都能准确获取当前线程引用;
通用性:可在任何代码块中调用(无调用环境限制),包括同步方法、普通方法、Lambda 表达式等。

2.注意事项

1.Thread.currentThread() 的核心用途 :该方法是线程内部获取自身实例的唯一方式,尤其在匿名线程(Lambda、匿名内部类)中,无法直接持有引用,只能通过该方法获取当前线程;
2.线程引用的有效性:线程执行完毕(状态变为TERMINATED)后,其实例引用仍有效,可通过引用查询线程的最终状态、ID 等信息,但无法再次启动(start()方法只能调用一次);

3**.避免内存泄露**:若长期持有已终止线程的引用,且该引用无法被垃圾回收,可能导致内存泄露,建议线程执行完毕后及时释放无用引用;

相关推荐
ALex_zry2 小时前
C++ 中多继承与虚函数表的内存布局解析
java·开发语言·c++
杰瑞不懂代码2 小时前
基于 MATLAB 的 AM/DSB-SC/VSB 模拟调制与解调仿真及性能对比研究
开发语言·matlab·语音识别·am·dsb-sc·vsb
霁月的小屋2 小时前
从Vue3与Vite的区别切入:详解Props校验与组件实例
开发语言·前端·javascript·vue.js
益达3212 小时前
JDBC实战优化|从基础增删改查到连接池的完整演进(附性能对比)
java·intellij-idea
趣知岛2 小时前
初识DeepSeek
开发语言·人工智能·deepseek
有一个好名字2 小时前
Spring Boot 依赖注入指南:多种方式深度剖析与代码演示
java·服务器·spring boot
superman超哥2 小时前
仓颉编译器优化揭秘:尾递归优化的原理与实践艺术
开发语言·后端·仓颉编程语言·仓颉·仓颉语言·尾递归·仓颉编译器
lkbhua莱克瓦242 小时前
基础-SQL-DML
开发语言·数据库·笔记·sql·mysql
独自破碎E2 小时前
说一下消息队列有哪些模型
java·开发语言