Java的多线程——多线程(二)

复习

1.1线程是什么?

  • 线程的定义与作用线程是轻量级的进程,它的出现是为了解决进程 "太重" 的问题 ------ 创建和销毁进程的开销较大,而线程的开销相对较小,能更高效地实现程序的并发执行。

1.2线程与进程的区别?

1.进程包含线程

2.进程是操作系统资源分配的基本单位

3.同一个进程中的多个线程之间,共用同一份资源(内存,文件)

1.3创建线程的方法第三种:内部匿名类

父类引用可以指向子类对象

java 复制代码
Thread t = new Thread() {

};
  1. new Thread() { ... } 创建的是 Thread 的一个匿名子类实例(匿名内部类本质是子类或接口实现类),它重写了 Threadrun() 方法,拥有自己的具体实现。
  2. Thread t 声明了一个 Thread 类型的变量 t,用于存储这个匿名子类的实例。由于匿名子类本身是 Thread 的子类,因此可以安全地赋值给父类类型的变量。
java 复制代码
package thread;

public class demo3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t;
        t =  new Thread(){
        public void run() {
            while (true) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("hello niming");
            }
        }


    };
    t.start();
        System.out.println("main______________________");
        while (true) {
            Thread.sleep(100);
            System.out.println("hello main");
        }
}

}

注意:Thread.sleep 这是静态的方法,一定要注意

用于一般是一次性的,用完就不需要了,就可以使用匿名内部类

1.4创建线程的第四种:Runnable 加上匿名内部类

java 复制代码
Runnable a = nwe Runnable() {
};

接口本身不能被实例化,但匿名内部类会隐式创建一个实现了该接口的子类 ,并同时创建这个子类的实例。因此,new 接口名() { ... } 本质上是创建了接口的匿名实现类的实例

java 复制代码
public class Demo4 {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        Thread t = new Thread(runnable);
        t.start();

        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

1.5创建线程的第五种方法:针对三和四的改进,Lambda表达式

java 复制代码
// 使⽤lambda 表达式创建 Runnable ⼦类对象
 
Thread t3 = new Thread(() -> System.out.println("使⽤匿名类创建 Thread ⼦类对象"));
 Thread t4 = new Thread(() -> {
 System.out.println("使⽤匿名类创建 Thread ⼦类对象");
 });

1.6多线程的优势

-增加运行速度 可以观察多线程在⼀些场合下是可以提高程序的整体运行效率的。

• 使用 System.nanoTime() 可以记录当前系统的纳秒级时间戳.

• se rial 串行的完成⼀系列运算. concurrency 使用两个线程并行的完成同样的运算.

java 复制代码
public class ThreadAdvantage {
    // 多线程并不一定能提高速度,可以观察,count 不同,实际的运行效果也是不同的
    private static final long count = 10_0000_0000;

    public static void main(String[] args) throws InterruptedException {
        // 使用并发方式
        concurrency();
        // 使用串行方式
        serial();
    }

    private static void concurrency() throws InterruptedException {
        long begin = System.nanoTime();

        // 利用一个线程计算 a 的值
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int a = 0;
                for (long i = 0; i < count; i++) {
                    a--;
                }
            }
        });
        thread.start();
        // 主线程内计算 b 的值
        int b = 0;
        for (long i = 0; i < count; i++) {
            b--;
        }
        // 等待 thread 线程运行结束
        thread.join();

        // 统计耗时
        long end = System.nanoTime();
        double ms = (end - begin) * 1.0 / 1000 / 1000;
        System.out.printf("并发:%.f 毫秒\n", ms);
    }

    private static void serial() {
        // 全部在主线程内计算 a、b 的值
        long begin = System.nanoTime();
        int a = 0;
        for (long i = 0; i < count; i++) {
            a--;
        }
        int b = 0;
        for (long i = 0; i < count; i++) {
            b--;
        }
        long end = System.nanoTime();
        double ms = (end - begin) * 1.0 / 1000 / 1000;
        System.out.printf("串行:%.f 毫秒\n", ms);
    }
}

并发:399.651856毫秒

串行:720.616911毫秒

2.Thread类及常见方法

Thread 类是JVM用来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的Thread对象与之关 联。 用我们上面的例子来看,每个执行流,也需要有⼀个对象来描述,类似下图所示,而Thread类的对象 就是用来描述⼀个线程执行流的,JVM会将这些Thread对象组织起来,用于线程调度,线程管理。

2.1 Thread的常见的构造方法

1.第一个构造方法Thread () 需要重写 run 方法

2.第二个方法就不需要重写 run 方法了

3.第四个第五个是给线程起名字

1.这几个名字就是他的线程,那为什么主线程(main)没有呢?

main方法执行完,程序就结束了(进程)

我们不结束main 方法不就行了

虽然虽然主线程结束了但是t1 ,t2,t3 线程没有结束,就会影响进程继续存在,这就是前台线程

JVM自带的线程,他们的存在不影响进程的结束,他们随着进程的就结束就结束了(因为其他的都执行完了,这个线程也没有存在的必要了),这种的就是后台进程

说白了就是我们程序员的可以控制的就是前台线程,不能控制结束的就是后端线程

2.1 Thread的几个常见属性

• ID是线程的唯一标识,不同线程不会重复

• 名称是各种调试工具用到

• 状态表示线程当前所处的⼀个情况,下面我们会进一步说明

• 优先级噶的线程理论上来说更容易被调度到

• 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有非后台线程结束后,才会结束运行。

• 是否存活,即简单的理解,为run方法是否运行结束了

• 线程的中断问题,下面我们进一步说明

java 复制代码
public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ": 我还活着");
                    Thread.sleep(1 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ": 我即将死去");
        });

        System.out.println(Thread.currentThread().getName() + ": ID: " + thread.getId());
        System.out.println(Thread.currentThread().getName() + ": 名称: " + thread.getName());
        System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());
        System.out.println(Thread.currentThread().getName() + ": 优先级: " + thread.getPriority());
        System.out.println(Thread.currentThread().getName() + ": 后台线程: " + thread.isDaemon());
        System.out.println(Thread.currentThread().getName() + ": 活着: " + thread.isAlive());
        System.out.println(Thread.currentThread().getName() + ": 被中断: " + thread.isInterrupted());

        thread.start();
        while (thread.isAlive()) {}
        System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());
    }
}

什么是后台进程?

这是关于 Java 守护线程(后台线程)的概念梳理:

  • 方法与含义isDaemon() 方法用于判断线程是否为守护线程(后台线程)。
  • 概念等价:守护线程 == 后台线程;与之相对的是前台线程。
  • 核心特点
    • 前台线程:若程序中还有前台线程在运行,进程不会结束;
    • 守护线程:是为前台线程服务的 "后台支持线程",当所有前台线程结束时,守护线程会被强制终止(即使自身任务未完成)。
    • 典型例子:JVM 的垃圾回收线程就是守护线程,它会在前台线程都结束后自动停止。

注意:我们创建的线程默认是前台线程包括main主线程,可以通过setDaemon()方法来修改

2.2setDaemon() 方法的使用

java 复制代码
package thread;

public class demo_setDaemon {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(true){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("test thread");
            }
        },"testFinished");
//要设置在start开始之前,设置为了后台进程
        t.setDaemon(true);
        t.start();


        for (int i = 0; i < 10000; i++) {
            Thread.sleep(10);
            System.out.println("888");
        }
    }

}

运行结果:

  1. 创建线程 t :线程 t 内部是一个无限循环,每秒打印一次 "test thread"(实际通过 sleep(1) 控制频率,接近持续运行),并命名为 "testFinished"

  2. 设置为守护线程 :通过 t.setDaemon(true)t 标记为守护线程,注意:此设置必须在 start() 之前调用,否则会抛出异常。

  3. 主线程逻辑 :主线程(main 线程)循环 10000 次,每次休眠 10 毫秒并打印 "888",执行完毕后主线程结束。

运行结果与结论:

  • 当主线程的循环执行完毕(打印完 10000 次 "888" 后),主线程作为前台线程结束。
  • 此时,程序中已没有其他前台线程运行,守护线程 t 会被强制终止(即使其内部的无限循环未执行完),整个进程随之结束。
核心结论:

守护线程的生命周期依赖于前台线程

  • 只要有一个前台线程在运行,守护线程就会继续工作;
  • 当所有前台线程都结束时,守护线程会被 JVM 自动终止,进程结束。
  • IDEA 与 Java 进程的关系 :IDEA 本身是一个 Java 进程;在 IDEA 中运行 Java 代码时,会通过 IDEA 进程创建一个新的 Java 进程,这两个进程存在父子关系
  • 进程与线程的区别(父子关系维度) :进程之间存在父子关系;而线程之间不存在父子关系。

2.3isAlive() 的使用

Java 中 Thread 对象与系统实际线程的关系,核心要点如下:

  • isAlive() 方法 :用于判断系统中的线程是否处于 "存活" 状态(即线程是否已启动且未终止)。

  • 一一对应关系 :Java 代码中创建的 Thread 对象,和系统底层的实际线程是一一对应的 (一个 Thread 对象对应一个系统线程)。

  • 生命周期差异Thread 对象的生命周期(作为 Java 对象的存在时间)和系统线程的生命周期(实际执行任务的时间)并不相同

    java 复制代码
    package thread;
    
    public class Demo8 {
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(() -> {
                for (int i = 0; i < 3; i++) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            t.start();
    
            while (true) {
                System.out.println(t.isAlive());
                Thread.sleep(1000);
            }
        }
    }

    执行结果

  • 线程执行逻辑与生命周期 :线程入口方法(如 run() 方法)中的逻辑执行完毕后,系统中对应的线程会随之销毁(操作系统层面的线程终止)。

  • 代码示例说明:示例中线程内的循环执行 3 次,每次休眠 1000 毫秒(共 3 秒),逻辑执行完毕后,该线程在系统中就会被销毁。

3秒后这时候这个线程已经执行完成了,但是这个线程还没有结束,所以就会出现这个情况

2.3.1多线程调度的随机性:

  • 现象 :在多线程程序中,主线程判断子线程 t 是否存活(通过 isAlive() 方法),子线程 t 运行时长约 3 秒,但主线程可能打印出 3 个或 4 个 "true"(表示子线程存活)。
  • 原因 :这是由于线程调度的随机性 导致的。操作系统对线程的调度是 "随机" 且 "抢占式" 的,主线程第四次打印 "true" 的时机和子线程 t 结束的时机可能存在重叠,因此结果不确定(可能 3 次,也可能 4 次)。

2.4 isInterrupted()

isInterrupted() 是 Java 中 Thread 类的一个实例方法,用于判断当前线程对象关联的线程 的中断标志位是否被设置。调用该方法后,不会清除中断标志位 (这是它和 Thread.interrupted() 方法的关键区别)

执行逻辑说明:
  1. 线程 t 启动后,每秒钟打印 "线程运行中...",并通过 !Thread.currentThread().isInterrupted() 判断是否继续循环。
  2. 主线程休眠 3 秒后,调用 t.interrupt() 设置线程 t 的中断标志位。
  3. 线程 t 若在 sleep 期间被中断,会捕获 InterruptedException,此时中断标志位会被自动清除;示例中手动调用 Thread.currentThread().interrupt() 重新设置标志位,确保循环条件 !isInterrupted()false,从而退出循环。
  4. 最后两次调用 t.isInterrupted() 都会返回 true,因为该方法不会清除中断标志位。
java 复制代码
public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            // 循环判断线程是否被中断
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("线程运行中...");
                try {
                    Thread.sleep(1000); // 模拟耗时操作
                } catch (InterruptedException e) {
                    // 捕获到中断异常后,中断标志位会被清除
                    // 若要保持中断状态,需手动重新设置
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
            }
            System.out.println("线程因中断退出循环");
        });

        t.start();

        Thread.sleep(3000); // 主线程休眠3秒
        System.out.println("准备中断线程 t");
        t.interrupt(); // 设置线程 t 的中断标志位

        // 查看中断状态(不会清除标志位)
        System.out.println("线程 t 是否被中断:" + t.isInterrupted()); 
        System.out.println("线程 t 是否被中断:" + t.isInterrupted()); 
    }
}

2.5启动一个线程-start()

之前我们已经看到了如何通过覆写 run 方法创建⼀个线程对象,但线程对象被创建出来并不意味着线 程就开始运行了。

这是对 Java 中 start() 方法的说明,可整理为以下要点:

  • 方法作用:用于启动一个线程。
  • 所属范畴:是 Java 标准库(由 JVM 提供)的方法。
  • 底层原理:本质上是调用操作系统的 API 来创建并调度线程,使线程进入可运行状态。

• 覆写run方法是提供给线程要做的事情的指令清单

• 线程对象可以认为是把李四、王五叫过来了

• 而调⽤start()方法,就是喊⼀声:"行动起来!",线程才真正独立去执行了。

Java 中 Thread 对象使用规则的说明,可整理为以下要点:

  • start() 方法的限制 :每个 Thread 对象只能调用一次 start() 方法来启动线程,若重复调用会抛出异常。
  • 对象的 "日抛" 特性 :每次需要创建新线程时,必须新建一个 Thread 对象,不能重复利用已启动过的 Thread 对象来创建新线程。

调用start方法,才真的在操作系统的底层创建出⼀个线程.

2.6中断一个线程

李四一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我 们需要增加⼀些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停⽌转账,那张三 该如何通知李四停止呢?这就涉及到我们的停止线程的方式了。 目前常见的有以下两种方式:

1.通过共享的标记来进行沟通

2.调用 interrupt() 方法来通知

示例-1:使用自定义的变量来作为标志位. 需要给标志位上加volatile关键字(这个关键字的功能后面介绍)

1. Lambda 中使用外部变量与 "变量捕获"

在 Lambda 表达式中,如果希望使用其定义作用域之外的变量,就会触发 "变量捕获" 语法。

  • 不同编程语言(如 C++、Java、Python 等)对变量捕获的规则有所差异:
    • 例如在 C++ 中,捕获方式分为值捕获[var])和引用捕获[&var]),值捕获会复制变量的值,引用捕获则是指向原变量的引用;

    • 在 Python 中,Lambda 对外部变量是闭包引用,会关联到变量的 "最新值",这可能引发一些执行时机相关的问题(如下文的回调场景)。

    • 在 Java 中,Lambda 表达式对外部变量的捕获规则是仅允许捕获 "final 或 effectively final" 的变量

    • final 变量 :显式用 final 关键字修饰的变量,其值不可变。

    • effectively final 变量 :未显式用 final 修饰,但在程序执行过程中其值从未被修改过的变量,Java 编译器会将其视为 "effectively final"。

2. Lambda 作为回调函数的执行时机

当 Lambda 作为回调函数时,其执行时机可能 "很晚"。

  • 文中举例 "操作系统真正创建出线程之后才会执行":比如在多线程编程中,用 Lambda 注册线程回调,Lambda 定义时外部变量的状态,和线程真正执行时变量的状态可能不同。
  • 典型问题:若 Lambda 捕获了循环变量(如 for 循环中的 i),且以引用方式捕获 (或 Python 式的闭包引用),当线程执行时,i 可能已经是循环结束后的值,导致逻辑不符合预期。

Java Lambda 捕获引用类型变量规则

  • 引用本身不可修改:Lambda 捕获的引用类型变量,不能修改其指向(即不能让该引用指向其他对象)。
  • 对象本体可修改:但引用指向的对象内部的属性、方法等是可以修改的。

拷贝意味着这样的变量就不适合进行修改

修改一方,另一方不会随之变化(本质上是两个变量)

这样的一边变,一边不变,会对程序员造成很大的困扰,Java的大佬就想办法设计了不让你修改

GC 是 "垃圾回收(Garbage Collection)" 的缩写,是 Java 等编程语言中自动管理内存的机制,主要作用如下:

  • 自动回收内存:识别并回收不再被使用的对象所占用的内存,避免程序员手动管理内存时容易出现的内存泄漏、 double 释放等问题。
  • 管理对象生命周期:像图中所说,对象本体由 GC 负责管理,不会随着方法结束而销毁,GC 会在合适的时机判断对象是否可回收,进而释放其内存。
java 复制代码
public class ThreadDemo {
    private static class MyRunnable implements Runnable {
        public volatile boolean isQuit = false;

        @Override
        public void run() {
            while (!isQuit) {
                System.out.println(Thread.currentThread().getName()
                        + ": 别管我,我忙着转账呢!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()
                    + ": 啊!险些误了大事");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        System.out.println(Thread.currentThread().getName()
                + ": 让李四开始转账。");
        thread.start();

        Thread.sleep(10 * 1000);
        System.out.println(Thread.currentThread().getName()
                + ": 老板来电话了,得赶紧通知李四对方是个骗子!");
        target.isQuit = true;
    }
}

Thread.interrupt() 方法的作用说明:

  • 功能一:修改中断标志位(boolean 变量) t.interrupt() 会修改线程 t 内部的中断标志位(一个 boolean 类型的变量),用于主动标记线程需要终止的意图。

  • 功能二:唤醒阻塞方法 除了设置中断标志位,它还能唤醒如 sleep 这类会让线程进入阻塞状态的方法。例如,若线程因 sleep 处于阻塞,调用 interrupt() 会使其退出阻塞,并抛出 InterruptedException,从而让线程有机会处理中断逻辑。

此时线程一般就会掀桌了 ,放置一个break 就不会了

示例-2:使用 Thread.interrupted() 或者Thread.currentThread().isInterrupted() 代替自定义标志位. Thread内部包含了⼀个boolean类型的变量作为线程是否被中断的标记

java 复制代码
public class ThreadDemo {
    private static class MyRunnable implements Runnable {
        @Override
        public void run() {
            // 两种方法均可以
            while (!Thread.interrupted()) {
            //while (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName()
                        + ":别管我,我忙着转账呢!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println(Thread.currentThread().getName()
                            + ":有内鬼,终止交易!");
                    // 注意此处的 break
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName()
                    + ":啊!险些误了大事");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        System.out.println(Thread.currentThread().getName()
                + ":让李四开始转账。");
        thread.start();
        Thread.sleep(10 * 1000);
        System.out.println(Thread.currentThread().getName()
                + ":老板来电话了,得赶紧通知李四对方是个骗子!");
        thread.interrupt();
    }
}

thread收到通知的方式有两种:

1.如果线程因为调用wait/join/sleep等方法而阻塞挂起,则以InterruptedException异常的形式通 知,清除中断标志 。当出现InterruptedException的时候,要不要结束线程取决于catch中代码的写法.可以选择忽 略这个异常,也可以跳出循环结束线程.

2.否则,只是内部的⼀个中断标志被设置,thread可以通过 Thread.currentThread().isInterrupted()判断指定线程的中断标志被设置,不清除中断标志 这种方式通知收到的更及时,即使线程正在sleep也可以马上收到

2.7等待⼀个线程-join()

有时,我们需要等待⼀个线程完成它的工作后,才能进行自己的下⼀步工作。例如,张三只有等李四 转账成功,才决定是否存钱,这时我们需要⼀个方法明确等待线程的结束。

java 复制代码
public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Runnable target = () -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName()
                            + ":我还在工作!");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":我结束了!");
        };

        Thread thread1 = new Thread(target, "李四");
        Thread thread2 = new Thread(target, "王五");
        System.out.println("先让李四开始工作");
        thread1.start();
        thread1.join();
        System.out.println("李四工作结束了,让王五开始工作");
        thread2.start();
        thread2.join();
        System.out.println("王五工作结束了");
    }
}

⼤家可以试试如果把两个join注释掉,现象会是怎么样的呢?

附录:

2.8 获取当前线程引用

这个方法我们已经非常熟悉了

java 复制代码
 public class ThreadDemo {
 public static void main(String[] args) {
 Thread thread = Thread.currentThread();
 System.out.println(thread.getName());
 }
}

2.9 休眠当前线程

也是我们比较熟悉⼀组方法,有⼀点要记得,因为线程的调度是不可控的,所以,这个方法只能保证 实际休眠时间是大于等于参数设置的休眠时间的。

java 复制代码
 public class ThreadDemo {
 public static void main(String[] args) throws InterruptedException {
 System.out.println(System.currentTimeMillis());
 Thread.sleep(3 * 1000);
 System.out.println(System.currentTimeMillis());
 }
 }

3. 线程的状态

3.1 观察线程的所有状态

线程的状态是⼀个枚举类型Thread.State

java 复制代码
 public class ThreadState {
 public static void main(String[] args) {
 for (Thread.State state : Thread.State.values()) {
 System.out.println(state);
 }
 }
 }

• NEW:安排了工作,还未开始行动

• RUNNABLE:可工作的.又可以分成正在工作中和即将开始工作

. • BLOCKED:这几个都表水排队等着其他事情

• WAITING:这几个都表示排队等着其他事情

• TIMED_WAITING:这几个都表⽰排队等着其他事情

• TERMINATED:工作完成了

3.2 线程状态和状态转移的意义

大家不要被这个状态转移图吓到,我们重点是要理解状态的意义以及各个状态的具体意思

还是我们之前的例子:

刚把李四、王五找来,还是给他们在安排任务,没让他们行动起来,就是NEW状态; 当李四、王五开始去窗口排队,等待服务,就进入到 RUNNABLE 状态。该状态并不表示已经被银行 工作人员开始接待,排在队伍中也是属于该状态,即可被服务的状态,是否开始服务,则看调度器的 调度; 当李四、王五因为⼀些事情需要去忙,例如需要填写信息、回家取证件、发呆⼀会等等时,进入BLOCKED 、 WATING 、 TIMED_WAITING 状态,至于这些状态的细分,我们以后再详解; 如果李四、王五已经忙完,为 TERMINATED 状态。 所以,之前我们学过的isAlive()方法,可以认为是处于不是NEW和TERMINATED的状态都是活着的

3.3 观察线程的状态和转移

观察1: 关注 NEW 、 RUNNABLE 、 1 2 3 4 TERMINATED 状态的转换

java 复制代码
public class ThreadStateTransfer {
 public static void main(String[] args) throws InterruptedException {
 Thread t = new Thread(() -> {
 for (int i = 0; i < 1000_0000; i++) {
            }
        }, "李四");
        System.out.println(t.getName() + ": " + t.getState());;
        t.start();
        while (t.isAlive()) {
            System.out.println(t.getName() + ": " + t.getState());;
        }
        System.out.println(t.getName() + ": " + t.getState());;
    }
 }

观察2:关注 WAITING 、 BLOCKED 、 TIMED_WAITING 状态的转换

java 复制代码
public static void main(String[] args) {
    final Object object = new Object();
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (object) {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }, "t1");
    t1.start();
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (object) {
                System.out.println("hehe");
            }
        }
    }, "t2");
    t2.start();
 }

使⽤jconsole可以看到t1的状态是TIMED_WAITING,t2的状态是BLOCKED

修改上面的代码,把 t1中的sleep换成wait

java 复制代码
 public static void main(String[] args) {
    final Object object = new Object();
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (object) {
                try {
                    // [
修改这⾥就可以了
!!!!!] 
                    // Thread.sleep(1000);
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }, "t1");
        ...
 }

使⽤jconsole可以看到t1的状态是WAITING

结论:

• BLOCKED表示等待获取锁,WAITING和TIMED_WAITING表示等待其他线程发来通知

. • TIMED_WAITING线程在等待唤醒,但设置了时限;WAITING线程在无线等待唤醒

相关推荐
热心网友俣先生5 分钟前
2026年第二十三届五一数学建模竞赛C题超详细解题思路+各问题可用模型推荐+部分模型结果展示
c语言·开发语言·数学建模
01漫游者10 分钟前
JavaScript函数与对象增强知识
开发语言·javascript·ecmascript
GottdesKrieges11 分钟前
OceanBase恢复常见问题
java·数据库·oceanbase
IGAn CTOU11 分钟前
Java高级开发进阶教程之系列
java·开发语言
leo825...14 分钟前
Claude Code Skills 清单(本地)
java·python·ai编程
csbysj202017 分钟前
SQL NULL 函数详解
开发语言
其实防守也摸鱼20 分钟前
CTF密码学综合教学指南--第三章
开发语言·网络·python·安全·网络安全·密码学
NGSI vimp21 分钟前
Java进阶——如何查看Java字节码
java·开发语言
We་ct1 小时前
深度剖析浏览器跨域问题
开发语言·前端·浏览器·跨域·cors·同源·浏览器跨域
身如柳絮随风扬1 小时前
多数据源切换实战:从业务场景到3种实现方案全解析
java·分布式·微服务