当前位置: 首页 > news >正文

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

复习

1.1线程是什么?

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

1.2线程与进程的区别?

1.进程包含线程

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

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

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

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

Thread t = new Thread() {};

  1. new Thread() { ... } 创建的是 Thread 的一个匿名子类实例(匿名内部类本质是子类或接口实现类),它重写了 Thread 的 run() 方法,拥有自己的具体实现。
  2. Thread t 声明了一个 Thread 类型的变量 t,用于存储这个匿名子类的实例。由于匿名子类本身是 Thread 的子类,因此可以安全地赋值给父类类型的变量。
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 加上匿名内部类

Runnable a = nwe Runnable() {
};

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

public class Demo4 {public static void main(String[] args) {Runnable runnable = new Runnable() {@Overridepublic 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表达式

// 使⽤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 使用两个线程并行的完成同样的运算.

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() {@Overridepublic 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方法是否运行结束了

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

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() 方法的使用

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 对象的存在时间)和系统线程的生命周期(实际执行任务的时间)并不相同
    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,因为该方法不会清除中断标志位。
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 会在合适的时机判断对象是否可回收,进而释放其内存。

public class ThreadDemo {private static class MyRunnable implements Runnable {public volatile boolean isQuit = false;@Overridepublic 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类型的变量作为线程是否被中断的标记

public class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic 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()+ ":有内鬼,终止交易!");// 注意此处的 breakbreak;}}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()

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

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 获取当前线程引用

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

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

2.9 休眠当前线程

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

 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

 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 状态的转换

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 状态的转换

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

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

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

 public static void main(String[] args) {final Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic 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线程在无线等待唤醒

http://www.dtcms.com/a/548772.html

相关文章:

  • 小企业也能用AI?低成本智能转型实战案例
  • ros2 播放 ros1 bag
  • 网页设计做一个网站设计之家官方网站
  • 基于STM32单片机 + DeepSeek-OCR 的智能文档扫描助手设计与实现
  • 微信小程序如何传递参数
  • 【数据结构】:数组及特殊矩阵
  • 记录一下微信小程序里使用SSE
  • API 接口安全:用 JWT + Refresh Token 解决 Token 过期与身份伪造问题
  • 云手机搬砖 高效采集资源
  • GitHub Actions CI/CD 自动化部署完全指南
  • Fastlane 结合 开心上架 命令行版本实现跨平台上传发布 iOS App
  • 广东营销网站建设服务公司军事信息化建设网站
  • Go Web 编程快速入门 14 - 性能优化与最佳实践:Go应用性能分析、内存管理、并发编程最佳实践
  • LeetCode每日一题——合并两个有序链表
  • 丽江市建设局官方网站门户网站开发需要多少钱
  • 边缘计算中评估多模态分类任务的延迟
  • 11.9.16.Filter(过滤器)
  • 光储充微电网能量管理系统:构建绿色、高效、安全的能源未来
  • MR30分布式IO在自动上料机的应用
  • 有些网站为什么可以做资讯建站工具交流
  • .NET周刊【10月第2期 2025-10-12】
  • 自动化文献引用和交叉引用高亮显示:Word VBA宏解决方案
  • 大数据离线处理:使用 Airflow 调度 Hive 脚本的工作流设计
  • 深入理解二叉搜索树:从原理到实现
  • Rust 泛型参数的实践与思考
  • AppML 案例:Employees 应用解析
  • 【Qt开发】布局管理器(一)-> QVBoxLayout垂直布局
  • CF练习记录~
  • 自动化测试 | 认识接口自动化封装中的YAML用例
  • dedecms做门户网站石家庄网站建站