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线程在无线等待唤醒

相关推荐
慌糖6 小时前
Java中JSON数据提取与处理
java·json
阿登林6 小时前
Unity3D与Three.js构建3D可视化模型技术对比分析
开发语言·javascript·3d
没有bug.的程序员6 小时前
Spring Boot 整合第三方组件:Redis、MyBatis、Kafka 实战
java·spring boot·redis·后端·spring·bean·mybatis
cherryc_7 小时前
JavaSE基础——第十二章 集合
java·开发语言
wgb04097 小时前
vxe table 升级之后页面数据不显示解决方法
java·前端·javascript
May’sJL7 小时前
Redis高可用-主从复制
java·redis·缓存
集成显卡7 小时前
Bun.js + Elysia 框架实现基于 SQLITE3 的简单 CURD 后端服务
开发语言·javascript·sqlite·bun.js
2501_938773997 小时前
Objective-C 类的归档与解档:NSCoding 协议实现对象持久化存储
开发语言·ios·objective-c
无敌最俊朗@7 小时前
SQlite:电影院售票系统中的主键(单列,复合)约束应用
java·开发语言·数据库