Java线程的基本操作

线程的基本操作

Java线程的常用操作都定义在Thread类中,包括一些重要的静态方法 和线程的实例方法 。下面我们来学习一下,线程的常用基本操作

1.线程名称的设置和获取

  1. 线程名称可以通过构造Thread的时候进行设置,也可以通过实例的方法setName()进行设置
  2. 线程名称一般在启动线程前进行设置,但是Java也允许在运行时设置线程名称
  3. Java中允许两个线程具有相同的名称,但是一般不建议这么做。
  4. 如果创建线程时没有指定名称,那么会自动分配线程名称 一般是 Thread-xxx,这种格式,但是不会重复

一个简单的设置线程的名称的案例

@Testpublic void test() {// 创建时指定线程名称Thread thread = new Thread(() -> {logger.info("开始执行了~");}, "t1");// 虽然在定义线程的时候,我们制定了线程名称,但是在启动线程的时候,我们还是可以修改线程的名称thread.start();thread.setName("t2->修改后的线程名称");logger.info("线程名称:{}", thread.getName());// 创建两个线程名称一样的数据new Thread(() -> {logger.info("线程3开始执行了~");}, "t3").start();new Thread(() -> {logger.info("线程3开始执行了~");}, "t3").start();// 不创建线程名称,系统会自动分配线程名称new Thread(() -> {logger.info("{},开始执行了~", Thread.currentThread().getName());}).start();}

image-20240311071115347

从输出的结果来看,如果没有进行手动赋值线程名称,那么Java会给对当前线程自动命名。并且可以创建两个线程名称相同线程,程序也不会出现异常,(不建议这样写)。

2.线程睡眠 Sleep操作

sleep的作用是让正在执行的线程进行休眠,让CPU去执行其他任务。

从线程的角度来进行说明的话,就是让 线程从执行状态 转为 阻塞状态,sleep()方法定义在Thread类中,是一组静态方法。

常用的休眠方法有两个

// 让正在执行的线程 休眠  xxxx 毫秒
public static native void sleep(long millis) throws InterruptedException;// 让正在执行线程 休眠 xxx毫秒 xxx纳秒
public static void sleep(long millis, int nanos) throws InterruptedException;

TimeUnit

这里我们还可以使用一个工具类,来将指定的时间格式转为毫秒

TimeUnit 是 Java 中的一个枚举类型,位于 java.util.concurrent 包中,用于表示时间单位。它提供了一种标准化的方式来处理不同时间单位之间的转换和操作。

TimeUnit 枚举定义了一些常用的时间单位,包括:

  1. NANOSECONDS: 纳秒
  2. MICROSECONDS: 微秒
  3. MILLISECONDS: 毫秒
  4. SECONDS: 秒
  5. MINUTES: 分钟
  6. HOURS: 小时
  7. DAYS: 天

TimeUnit 的主要作用是提供了一些方法,使得时间单位之间的转换变得方便。其中一些主要的方法包括:

  • toNanos(long duration): 将给定时间转换为纳秒。
  • toMicros(long duration): 将给定时间转换为微秒。
  • toMillis(long duration): 将给定时间转换为毫秒。
  • toSeconds(long duration): 将给定时间转换为秒。
  • toMinutes(long duration): 将给定时间转换为分钟。
  • toHours(long duration): 将给定时间转换为小时。
  • toDays(long duration): 将给定时间转换为天。

此外,TimeUnit 还提供了一些方法来进行时间的延迟操作,比如:

  • sleep(long timeout): 让当前线程休眠指定的时间,单位是当前 TimeUnit 实例所表示的时间单位。

    •     /*** 其实核心 还是调用了Thread.sleep() 只是帮我们转化了 毫秒 和 纳秒* Performs a {@link Thread#sleep(long, int) Thread.sleep} using* this time unit.* This is a convenience method that converts time arguments into the* form required by the {@code Thread.sleep} method.** @param timeout the minimum time to sleep. If less than* or equal to zero, do not sleep at all.* @throws InterruptedException if interrupted while sleeping*/public void sleep(long timeout) throws InterruptedException {if (timeout > 0) {long ms = toMillis(timeout);int ns = excessNanos(timeout, ms);Thread.sleep(ms, ns);}}
      
  • timedJoin(Thread thread, long timeout): 在指定的时间内等待另一个线程终止。

  • timedWait(Object obj, long timeout): 在指定的时间内等待在对象上的某个线程唤醒当前线程。

下面通过一个小案例来了解一下TimeUnit

@Test
public void test2() {// 测试TimeUnitlogger.error("1秒 -> {}毫秒", TimeUnit.SECONDS.toMillis(1));logger.error("1分钟 -> {}秒", TimeUnit.MINUTES.toSeconds(1));logger.error("1小时 -> {}分钟", TimeUnit.HOURS.toMinutes(1));logger.error("1天 -> {}小时", TimeUnit.DAYS.toHours(1));logger.error("1周 -> {}天", TimeUnit.DAYS.toDays(7));// 同时TimeUnit还为我们提供了sleep方法try {logger.error("开始睡眠1秒,当前时间:{}", System.currentTimeMillis());TimeUnit.SECONDS.sleep(1);logger.error("睡眠结束,当前时间:{}", System.currentTimeMillis());} catch (InterruptedException e) {e.printStackTrace();}} 

image-20240311073056032

  1. 通过TimeUnit.SECONDS.toMillis(1)将1秒转换为毫秒,并使用日志输出这个转换结果。
  2. 使用TimeUnit.MINUTES.toSeconds(1)将1分钟转换为秒,并输出结果。
  3. 使用TimeUnit.HOURS.toMinutes(1)将1小时转换为分钟,并输出结果。
  4. 使用TimeUnit.DAYS.toHours(1)将1天转换为小时,并输出结果。
  5. 使用TimeUnit.DAYS.toDays(7)将1周转换为天,并输出结果。
  6. 使用了TimeUnit.SECONDS.sleep()方法,当前线程睡眠1秒,然后再次记录当前时间。在这个过程中,如果线程在睡眠期间被中断,它会抛出InterruptedException异常,所以使用了try-catch块来捕获可能抛出的异常,并在捕获到异常时打印出栈信息。

InterruptedException

sleep方法会有方法有可能抛出InterruptedException异常。当当前线程在sleep()期间被另一个线程调用了interrupt()方法时,就会抛出InterruptedException异常。

以下是关于InterruptedException异常的一些要点:

  1. 触发条件InterruptedException异常通常在调用Thread.sleep()的线程被中断时抛出。这个中断是由其他线程通过调用interrupt()方法引起的。
  2. 处理方式:通常,在捕获到InterruptedException异常时,你需要决定如何处理中断。可能的处理方式包括恢复线程的中断状态终止线程,或者采取其他的线程中断处理逻辑
  3. 恢复中断状态:如果你在catch块中捕获了InterruptedException异常,一种常见的做法是在处理完异常后恢复线程的中断状态。你可以通过调用Thread.currentThread().interrupt()来重新设置线程的中断状态,以确保其他部分的代码能够正确响应中断请求。

Sleep

下面是一个简单的线程休眠的案例

@Testpublic void test3() throws InterruptedException {new Thread(() -> {logger.info("线程1开始执行了~");}, "t1").start();new Thread(() -> {// 线程2睡眠10秒try {logger.error("线程2开始睡眠了~");Thread.sleep(TimeUnit.SECONDS.toMillis(10));} catch (InterruptedException e) {throw new RuntimeException(e);}logger.info("线程2开始执行了~");}, "t2").start();new Thread(() -> {// 线程3睡眠10秒try {logger.error("线程3开始睡眠了~");Thread.sleep(TimeUnit.SECONDS.toMillis(10));} catch (InterruptedException e) {throw new RuntimeException(e);}logger.info("线程3开始执行了~");}, "t3").start();new Thread(() -> {logger.info("线程4开始执行了~");}, "t4").start();// 主线程睡眠最大值,保证子线程执行完毕Thread.sleep(Long.MAX_VALUE);}

image-20240311074423438

从运行结果,我们可以观察到一个线程,虽然 线程2线程3,线程3先开始执行休眠操作,但是 当休眠结束,线程3并没有立即得到执行,因为此时线程2获取到CPU时间片了。

由此,我们可以得出一个结论,当线程睡眠时间满后,线程不一定立即得到执行,因为此时CPU可能正在执行其他任务,此时的线程首先是进入就绪状态,等待CPU分配时间片便有机会执行(说白了,就是需要重新竞争获取CPU时间片)

image-20240311075936173
在这里插入图片描述

3.interrupt操作

在Java中,stop() 方法是一种已被废弃的方法,它用于停止线程的执行。然而,由于该方法可能会导致线程不可预测的状态和资源泄漏,不推荐使用。

相反,Java提供了一种更安全和可控的方法来停止线程,即使用 interrupt() 方法。interrupt() 方法是 Thread 类的一个实例方法,用于请求中断线程的执行。当调用线程的 interrupt() 方法时,会设置线程的中断标志,然后根据线程的状态来决定如何响应中断请求。

以下是一些关于interrupt() 方法一些介绍

  1. 中断标志(Interrupt Flag):每个线程都有一个中断标志,它表示线程是否被请求中断。中断标志的初始值为 false。当调用线程的 interrupt() 方法时,中断标志会被设置为 true

  2. 中断异常(Interrupted Exception):某些阻塞操作(如 sleep()wait()join() 等)会在线程被中断时抛出 InterruptedException 异常。这样一来,线程在执行这些阻塞操作时,可以通过捕获 InterruptedException 来检查中断状态,并做出相应的响应。

  3. 检查中断状态:线程可以通过调用 Thread 类的 isInterrupted() 方法来检查自己的中断状态。这个方法返回一个布尔值,指示线程的中断状态。

  4. 中断响应:线程可以根据中断状态选择不同的响应方式。常见的响应方式包括终止线程、清理资源、抛出异常等。线程可以在执行过程中定期检查中断状态,以决定是否终止执行。

  5. 不会立即打断正在运行的线程:当线程正在运行中时,调用 interrupt() 方法不会立即停止线程的执行。相反,它会在线程的执行过程中设置中断标志,然后线程可以根据中断标志来决定是否终止执行

    在线程的执行代码中,可以通过使用 isInterrupted() 方法来检查中断标志,并根据需要采取适当的行动。例如,可以在循环迭代或关键操作之间检查中断标志,以便在收到中断请求时安全地停止线程的执行。

下面通过一个案例来观察一下

打断正在sleep的线程

首先,我们在代码的开头创建了一个新的线程 thread,该线程会打印一条日志消息,然后调用 Thread.sleep() 方法来模拟一个耗时的操作 10s。

接下来,主线程调用 thread.start() 启动了新线程,然后通过 Thread.sleep() 方法使主线程休眠了2秒。

在主线程休眠结束后,调用 thread.interrupt() 方法来中断线程。这会设置线程的中断标志。

当线程正在执行 Thread.sleep() 方法时,如果线程被中断,Thread.sleep() 方法会抛出 InterruptedException 异常。在代码中,我们通过捕获 InterruptedException 来处理中断异常,并在日志中打印一条相应的错误消息。

	@Testpublic void test6() {// 使用interrupt()方法中断线程 主要是打断正在sleep的线程Thread thread = new Thread(() -> {logger.info("线程1开始执行了~");try {Thread.sleep(TimeUnit.SECONDS.toMillis(10));} catch (InterruptedException e) {// 当线程正在sleep的时候,调用interrupt()方法会抛出InterruptedException异常logger.error("线程1被打断了~");throw new RuntimeException(e);}}, "t1");// 启动线程thread.start();// 主线程休眠2stry {Thread.sleep(TimeUnit.SECONDS.toMillis(2));} catch (InterruptedException e) {throw new RuntimeException(e);}// 打断线程thread.interrupt();}

image-20240311221610091

打断正常运行的线程

Thread.currentThread().isInterrupted() 是一个用于检查当前线程的中断状态的方法。它是 Thread 类的一个静态方法,返回一个布尔值,表示当前线程的中断状态。

具体来说,Thread.currentThread() 返回当前正在执行的线程的引用,然后调用 isInterrupted() 方法来检查该线程的中断状态。如果中断标志被设置为 true,则 isInterrupted() 方法返回 true;否则,返回 false

这样的话,我们就可以优雅处理线程被打断后,是否要继续执行,还是退出

	/*** 使用interrupt()方法中断正常运行的线程*/@Testpublic void test7() {Thread thread = new Thread(() -> {logger.info("线程1开始执行了~");while (true) {logger.error("线程1正在执行~");// 当线程正在运行的时候,调用interrupt()方法会打断线程if (Thread.currentThread().isInterrupted()) {logger.error("线程1被打断了~");logger.error("线程1开始处理后事,优雅退出~");break;}}}, "t1");// 启动线程thread.start();// 主线程休眠2stry {Thread.sleep(TimeUnit.SECONDS.toMillis(2));} catch (InterruptedException e) {throw new RuntimeException(e);}// 打断线程thread.interrupt();}

image-20240311222608700

4.join

在Java中线程的合并,是一个比较难理解的概念。那什么是线程合并呢?在Java中,线程合并(Thread Join)是一种线程间的协作机制,它允许一个线程等待另一个线程执行完成后再继续执行。通过线程合并,可以有效地控制多个线程的执行顺序和并发性。

Java的Thread类提供了join()方法来实现线程的合并。当一个线程调用另一个线程的join()方法时,它会进入等待状态,直到被调用join()方法的线程执行完毕。在这种等待过程中,当前线程会阻塞,不会继续执行其他操作,直到被等待的线程执行完成或指定的超时时间到达。

join()方法有三种重载形式:

  1. public final void join() throws InterruptedException: 无参方法,让当前线程等待被调用线程执行完成。

    1. // 无参数的join其实在内部 调用了第二种方式 只是赋值了一个0
      public final void join() throws InterruptedException {join(0);
      }
      
  2. public final synchronized void join(long millis) throws InterruptedException : 带超时参数的方法,让当前线程等待被调用线程执行完成,最多等待指定的时间(以毫秒为单位)。

  3. public final synchronized void join(long millis,int nanos) throws InterruptedException: 带超时参数的方法,让当前线程等待被调用线程执行完成,最多等待指定的时间(以毫秒 + 纳秒为单位)。

下面我们依旧是通过代码来了解一下join的用法

等待线程执行完毕

/*** 使用join控制线程执行顺序* 案例协作计算(线程2 必须 等待线程1 执行结束 才能进行执行)* 通过合理使用join() 方法可以实现多个线程之间的同步与协作,并保证主程序在满足特定条件后能够正确执行。*/@Testpublic void test5() {// 在调用join方法前确保被调用的线程已经启动,否则可能导致不可预测结果。// 在使用带超时参数的join方法时要处理InterruptedException异常。// 在使用多个thread.join()组合等待不同子任务时要考虑其顺序和逻辑合理性。Thread t1 = new Thread(() -> {logger.info("线程1开始执行了~");// 线程1 休眠5stry {Thread.sleep(TimeUnit.SECONDS.toMillis(5));} catch (InterruptedException e) {throw new RuntimeException(e);}}, "t1");Thread t2 = new Thread(() -> {// 使用isAlive()方法判断线程是否存活logger.error("{}的是否存活:{}", t1.getName(), t1.isAlive());if (t1.isAlive()) {try {t1.join();// 线程2 执行的前提是 线程1 执行完毕logger.error("线程2开始执行了~");} catch (InterruptedException e) {throw new RuntimeException(e);}}}, "t2");t1.start();t2.start();// 让主线程等待 t2 执行完毕try {t2.join();} catch (InterruptedException e) {throw new RuntimeException(e);}}

首先,创建了两个线程 t1t2。在 t1 线程中,通过 Thread.sleep() 方法模拟一个耗时的操作,这里是休眠5秒。

t2 线程中,首先使用 t1.isAlive() 方法判断 t1 线程是否还存活。如果 t1 线程仍然存活,即尚未执行完毕,那么调用 t1.join() 方法来等待 t1 线程执行完毕。只有当 t1 线程执行完毕后,t2 线程才会继续执行,并输出 “线程2开始执行了~”。

在主线程中,通过调用 t2.join() 方法,主线程会等待 t2 线程执行完毕后才继续执行。这样可以保证主程序在满足特定条件(这里是等待 t2 线程执行完毕)后能够正确执行。

需要注意的是,在调用 join() 方法之前,需要确保被调用的线程已经启动,否则可能会导致不可预测的结果。

image-20240311230517713

image-20240311224208807

限时等待线程执行结束

其实这里限时的意思,就是 会在确定的时间内取等待线程执行,如果超过这个时间线程还是没有执行完毕,那么另外的线程也不会去等待你执行完毕。他们会在时间到期后,立即去获取CPU时间片,去执行任务。

这里的代码 主要就是针对于 join添加 等待时间,代码和上面还是保持一致

在使用带超时参数的 join() 方法时,需要处理 InterruptedException 异常。如果使用多个 join() 方法组合等待不同的子任务时,还需要考虑其顺序和逻辑的合理性。

// 让主线程等待 t2 执行完毕
try {// 我们这里只等待2s 如果线程2没有执行完毕,那么主线程就会继续执行t2.join(TimeUnit.SECONDS.toMillis(2));
} catch (InterruptedException e) {throw new RuntimeException(e);
}
logger.error("主程序执行结束~");

image-20240311224812294

5.yield

线程的yield(让步)操作的作用是:让出当前正在执行的线程,让出CPU的执行权,使得CPU去执行其他线程

当线程调用yield()方法时,它会暗示线程调度器将执行权交给其他具有相同优先级的线程。具体来说,yield()方法的作用是让出当前线程的执行权,以便让其他具有相同优先级的线程有机会执行。

JVM层面,线程调用yield()方法后,线程的状态仍然是RUNNABLERUNNABLE状态表示线程是可运行的,并且已经获得了所有必需的资源,可以在任何时候被线程调度器选择执行。调用yield()方法后,线程仍然处于准备好继续执行的状态,只是表示它愿意让出CPU执行权,以便给其他线程更多的机会。

操作系统层面yield()方法会影响操作系统的线程调度。当线程调用yield()方法时,操作系统会将该线程从"运行"状态转换为"就绪"状态,从而触发操作系统重新进行线程调度,选择下一个要执行的线程。这样可以使得其他线程有更多的机会获取CPU的执行时间片。

需要注意的是,yield()方法只是对线程调度器的一种建议,它并不能保证当前线程一定会让出CPU也不能保证其他线程会立即执行。具体的线程调度行为取决于操作系统和JVM的实现,并可能因不同的操作系统和硬件环境而有所差异。

综上所述,yield()方法的作用是让出当前线程的执行权,以便让其他具有相同优先级的线程有机会执行。在JVM层面,线程状态仍然是RUNNABLE,表示线程仍然准备好继续执行。在操作系统层面,yield()方法会触发操作系统的线程调度,让操作系统重新选择要执行的线程。然而,具体的线程调度行为取决于操作系统和JVM的实现。

下面通过一个简单的案例,来了解一下yield()方法

	@Testpublic void test8(){Thread t1 = new Thread(() -> {for (int i = 0; i < 5; i++) {logger.error("Thread 1: {}", i);Thread.yield(); // 让出执行权}});Thread t2 = new Thread(() -> {for (int i = 0; i < 5; i++) {logger.error("Thread 2: {}", i);Thread.yield(); // 让出执行权}});t1.start();t2.start();}

这个案例,创建了两个线程 t1t2。每个线程都会循环打印一系列消息,并在每次循环后调用yield()方法让出执行权。

当程序运行时,两个线程交替执行。由于调用了yield()方法,每个线程在打印一条消息后会主动让出CPU执行权,给其他线程执行的机会。这样,两个线程会交替地执行打印操作,而不是一个线程连续执行完再执行另一个线程。

请注意,由于线程调度的具体行为依赖于操作系统和JVM的实现,因此实际的执行顺序可能会因系统环境的不同而有所差异。此案例仅用于说明yield()方法的作用,以及它在多线程场景下可能产生的效果。

image-20240311233433789

综合上面来看,yeild()方法有以下特点

  1. yield()方法是Thread类中的一个静态方法,用于让出当前线程的执行权。
  2. 调用yield()方法会主动让出CPU执行权,使得其他具有相同优先级的线程有机会执行。
  3. yield()方法的调用并不会将线程状态转变为"就绪"状态,而是保持为"运行"状态(RUNNABLE)。(从JVM角度看,从操作系统角度看,就是 “运行状态” -> ”就绪状态“)
  4. yield()方法只是向线程调度器发出一个提示,表示当前线程愿意让出CPU执行权,但并不保证其他线程会立即执行。
  5. 具体的线程调度行为取决于操作系统和JVM的实现,可能因不同的操作系统和硬件环境而有所差异。
  6. yield()方法的使用应谨慎,过度使用可能导致线程之间的不合理竞争和不稳定性。
  7. yield()方法主要用于改善多线程场景下的线程间公平性和资源利用率,避免某个线程长时间独占CPU执行时间片。
  8. yield()方法的效果因系统环境和线程的优先级而异,不能完全控制线程的执行顺序和频率。

6.线程的daemon操作

在Java中,线程可以分为两种类型:用户线程(User Thread)和守护线程(Daemon Thread)。

  1. 用户线程(User Thread):
    • 用户线程是最常见的线程类型。当Java虚拟机(JVM)启动时,默认情况下创建的线程就是用户线程。
    • 用户线程的目的是执行应用程序的业务逻辑,它们不会影响JVM的退出,即使所有的用户线程都执行完毕,JVM仍然会继续运行,直到所有的守护线程也执行完毕或被中止。
    • 用户线程的执行结果不会影响JVM的终止,因此它们具有较长的生命周期。
  2. 守护线程(Daemon Thread):
    • 守护线程是一种特殊类型的线程,在启动之前需要通过setDaemon(true)方法将线程设置为守护线程。
    • 守护线程的目的是为其他线程提供服务支持,它们通常用于执行一些后台任务或周期性的维护工作。
    • 当所有的用户线程执行完毕后,JVM会检查是否只剩下守护线程在运行。如果只剩下守护线程,JVM会终止守护线程并退出。
    • 守护线程的生命周期依赖于用户线程的存在。当所有的用户线程执行完毕后,守护线程会随之被中止。

守护线程的基本操作

在Thread类中,有一个实例属性和两个方法,是专门用于对守护线程进行相关操作

  • 实例属性daemondaemonThread类的一个实例属性,用于标识线程是否为守护线程。它是一个布尔值,true表示线程是守护线程,false表示线程是用户线程。可以通过isDaemon()方法获取线程的守护线程标识。
Thread thread = new Thread();
boolean isDaemon = thread.isDaemon(); // 获取线程是否为守护线程
  • 方法setDaemon(boolean on)setDaemon(boolean on)方法用于将线程设置为守护线程或用户线程。如果on参数为true,则将线程标记为守护线程;如果on参数为false,则将线程标记为用户线程。该方法必须在线程启动之前调用。
Thread thread = new Thread();
thread.setDaemon(true); // 将线程设置为守护线程
  • 方法isDaemon()isDaemon()方法用于判断线程是否为守护线程。如果返回true,则表示线程是守护线程;如果返回false,则表示线程是用户线程。
Thread thread = new Thread();
boolean isDaemon = thread.isDaemon(); // 获取线程是否为守护线程

下面通过一个案例来了解以下守护线程基本操作

@Test
public void test9() {Thread t1 = new Thread(() -> {try {while (true) {logger.error("Daemon Thread is running...");Thread.sleep(1000);}} catch (InterruptedException e) {e.printStackTrace();}}, "t1");// 设置为守护线程t1.setDaemon(true);t1.start();// 主线程休眠3秒钟try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}// 当主线程结束后,会检查所是否还有其他线程在执行,如果没有,那么守护线程也会结束logger.error("主线程结束了~");
}

在上述案例中,通过创建一个匿名内部类,定义了一个守护线程的逻辑代码。在守护线程的执行过程中,使用Thread.sleep(1000)使线程每秒输出一条消息。

然后,使用Thread类的实例daemonThread来创建守护线程。通过setDaemon(true)将线程设置为守护线程,再通过start()方法启动守护线程。

主线程休眠3秒后结束,并输出一条消息。由于只剩下守护线程在运行,JVM会终止守护线程并退出。

执行上述代码,你会看到守护线程每秒输出一条消息,然后主线程休眠3秒后结束,守护线程也随之中止。

image-20240311235842541

守护线程和用户线程的关系

image-20240312000251602

守护线程和用户线程的关系,有以下几个要点:

  • 守护线程的创建和设置:可以通过Thread类的构造函数或使用setDaemon(true)方法将线程设置为守护线程。
  • 守护线程的特点:
    • 守护线程的优先级通常较低,当资源竞争时,用户线程更有可能获得CPU的执行时间片。
    • 守护线程不能持有任何会导致程序不正常终止的资源,例如打开的文件或数据库连接。
    • 守护线程的代码应该谨慎处理异常,因为它们往往无法捕获到由于JVM退出而导致的未捕获异常。
  • 用户线程的特点:
    • 用户线程的生命周期不受其他线程的影响,即使用户线程执行完毕,JVM仍然会继续运行直到所有的守护线程也结束。

总结来说,守护线程是为用户线程提供支持和服务的线程,其生命周期依赖于用户线程。当所有的用户线程执行完毕后,守护线程会随之中止。用户线程的生命周期不受其他线程的影响,即使用户线程执行完毕,JVM仍然会继续运行直到所有的守护线程也结束。\

守护线程注意事项

  1. 守护线程的作用:守护线程通常用于在程序运行时提供一种服务或者后台支持的功能。它们不应该执行一些需要确保完整性和稳定性的任务。
  2. 生命周期与非守护线程的区别:当所有非守护线程结束时,Java虚拟机会自动退出,而不管是否有守护线程仍在运行。这意味着守护线程的生命周期不应该影响整个程序的生命周期。
  3. 守护线程和用户线程之间的区别
    • 当仅剩下守护线程运行时,Java虚拟机会退出。
    • 守护线程通常用于执行一些支持性任务,比如垃圾回收器线程就是一个守护线程。
  4. 注意事项
    • 守护线程不能用于执行文件I/O或者数据库操作等需要资源清理或状态管理的任务,因为在Java虚拟机退出时,这些操作可能无法完成,导致数据丢失或不一致。
    • 守护线程应该谨慎地使用,确保不会在应用程序退出时导致数据丢失或者应用程序状态不一致的情况发生。-

*守护线程创建的也是守护线程

在Java中,当创建一个新线程时,如果该线程是通过守护线程创建的,那么它也将被设置为守护线程。换句话说,子线程的属性会继承自创建它的父线程。这意味着,如果一个线程是守护线程,那么它创建的任何子线程也将被设置为守护线程。

例如,如果您创建一个守护线程,并且在守护线程中启动了其他线程,那么这些新线程也将被视为守护线程,即使您没有明确地将它们设置为守护线程。

这种继承特性可以确保整个线程树的一致性,从而避免不必要的复杂性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/738993.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NOIP2018-S-DAY1-3-赛道修建(洛谷P5021)的题解

目录 题目 原题描述&#xff1a; 题目描述 输入格式 输出格式 输入输出样例 主要思路&#xff1a; check&#xff1a; 真正的code: 原题描述&#xff1a; 题目描述 C 城将要举办一系列的赛车比赛。在比赛前&#xff0c;需要在城内修建 条赛道。 C 城一共有 个路…

rancher里的ingress如何配置gzip压缩

方案一&#xff0c;未试验成功&#xff0c;但配置过程值得记录一下 通过配置configmap&#xff0c;然后在ingress的deployment里引用configmap实现。 参考文章 创建configmap apiVersion: v1 kind: ConfigMap metadata:name: nginx-ingress-controllerannotations:{} # k…

WPF Button去除按钮边框,添加下划线

<Button Width"45" Height"25" FontSize"20" Background"Transparent" BorderBrush"Transparent" Foreground"#FFC9A322" Click"Btn_Retry_Click" ><TextBlock><Underline>重试</…

01_lombok review

文章目录 Lombok父子工程ide中的Maven基础配置前置知识储备 Lombok 怎么引入Lombok依赖&#xff1a; step1&#xff1a;引入Lombok依赖 eg&#xff1a; <dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok<…

数据库管理-第159期 Oracle Vector DB AI-10(20240311)

数据库管理159期 2024-03-11 数据库管理-第159期 Oracle Vector DB & AI-10&#xff08;20240311&#xff09;1 其他distance函数2 实例演示使用其他函数寻找最近向量点函数变体简写语法 总结 数据库管理-第159期 Oracle Vector DB & AI-10&#xff08;20240311&#x…

网络安全AI智能体公司「云起无垠」获数千万元天使+轮融资,致力于大模型与网络安全深度融合的技术研究

「云起无垠」致力于打造最懂安全的AI智能体&#xff0c;通过持续运营的工具、知识以及记忆引擎&#xff0c;不断提升智能体对用户安全场景的理解&#xff0c;以达到易于使用、自我学习、自主行动的特性&#xff0c;助力企业自动化执行各类安全任务&#xff0c;让软件更安全&…

解决:黑马webpack视频中出现的问题总结

问题 1 ERROR in main Module not found: Error: Can‘t resolve ‘./src‘ 解决 Webpack 中 ERROR in main Module not found: Error: Can‘t resolve ‘./src‘ 问题 黑马AJAX-Node.js-Webpack教学视频&#xff08;BV1MN411y7pw 其中P98&#xff09;中webpack部分&#xff0c…

代理IP如何应对自动化测试和爬虫检测

目录 一、代理IP在自动化测试和爬虫中的作用 二、代理IP的优缺点分析 1.优点 2.缺点 三、应对自动化测试和爬虫检测的策略 1.选择合适的代理IP 2.设置合理的请求频率和间隔 3.模拟人类行为模式 4.结合其他技术手段 四、案例与代码示例 五、总结 在自动化测试和爬虫开…

传递函数硬件化

已知一个系统的传递函数&#xff0c;如何进行硬件化呢&#xff1f; 只需要将传递函数离散化&#xff0c;得到差分方程&#xff0c;就可以根据差分方程进行硬件设计。 通过例子说明&#xff1a; 得到差分方程后&#xff0c;其中y(k)/y(k-1)/y(k-2)/u(k-1)/u(k-2)等代表不同周期…

软考如何选择?信息系统项目管理师值得选吗?

软考有五大专业方向和三个等级&#xff0c;一共有27个资格认证&#xff0c;如果没有进行深入了解的情况下从这27个中选择一个可能会为难。一般情况下&#xff0c;我们进行选择的时候都会结合自己所学的专业方向、所在的工作岗位发展方向去选择&#xff0c;但是我给大家的建议是…

华为OD机试 - 垃圾信息拦截(Java 2024 C卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述1、输入2、输出3、说明 四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&a…

Node.js 安装和配置

一、Node.js 下载和安装 1.1 下载 Node.js 打开 Node.js — Download (nodejs.org)&#xff0c; 选择合适的版本。 1.2 安装 Node.js 双击 node-v20.11.1-x64.msi&#xff0c;安装 Node.js &#xff08;自定义安装目录&#xff0c;我的修改为 D:\Program Files\nodejs\&…

Google发布创新AI工具Path Foundation和Derm Foundation,突破医学影像解读瓶颈,开启病理学与皮肤科研究新纪元

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

解压常见_gzip:stdin:not in gzio format:怀疑下文件是否损坏

此次的主角文件是&#xff1a;pin-2.14-71313-gcc.4.4.7-linux.tar.gz 结论&#xff1a;文件后缀没问题&#xff0c;就先怀疑下是不是文件损坏了 ls指令看不出任何端倪 文件名、后缀都正常 解压出现报错 瞅瞅文件大小 du -h <文件名> 呦呵 4kb&#xff0c;和应该的大…

基于SpringBoot的信息技术知识赛系统设计与实现

该系统使用的到的开发工具为Eclipse&#xff0c;使用的数据库为Mysql&#xff0c;使用的框架为SpringBoot框架。 系统主要实现了如下功能&#xff1a; 用户信息管理 如图5.1显示的就是用户信息管理页面&#xff0c;此页面提供给管理员的功能有&#xff1a;用户信息的查询管理…

波司登高德康:以有“韧性”的创新应变市场新浪潮

伴随着消费升级&#xff0c;羽绒服市场需求日益旺盛。愈发多元化的需求对于企业发展也有着更高的要求。如何更好推动行业多元化发展&#xff0c;可以从波司登的品牌升级、产品创新、模式创新、数字技术创新、绿色发展创新等方面窥得一二。 高德康总裁接受新华网主持人采访 对此…

Codeforces Round 933 (Div. 3)C:Rudolf and the Ugly String

题目链接&#xff1a;Dashboard - Codeforces Round 933 (Div. 3) - Codeforces 解题思路&#xff1a; 解题思路&#xff1a; 题目大概意思是字符串中最少去掉几个单词可以使字符串变漂亮&#xff0c;其实只要找“map"和”pie“这两个单词数量&#xff0c;注意判断&quo…

【PyTorch][chapter 22][李宏毅深度学习]【无监督学习][ WGAN]【理论一】

简介&#xff1a; 2014年Ian Goodfellow提出以来&#xff0c;GAN就存在着训练困难、生成器和判别器的loss无法指示训练进程、生成样本缺乏多样性等问题。从那时起&#xff0c;很多论文都在尝试解决&#xff0c;但是效果不尽人意&#xff0c;比如最有名的一个改进DCGAN依靠的是对…

Kafka的基本介绍以及扩展

文章目录 基本操作新增Topic查询Topic修改Topic删除Topic 生产者和消费者创建生产者创建消费者 Broker扩展Producer扩展Topic、Partition、Message扩展存储策略容错机制 基本操作 新增Topic 指定两个分区&#xff0c;两个副本&#xff0c;replication不能大于集群中的broker数…

【MAC】MacOS M2 芯片的Mysql 数据库安装与使用

1.下载 https://downloads.mysql.com/archives/community/ 选择ARM的 2.安装 在安装到最后一步&#xff1a;configuration 一定要选择Use Legacy Password Encryption。 一定要记得输入密码&#xff0c;这个密码也是登陆mysql的密码&#xff0c;非常重要。备注&#xff1a;…