Java线程的基本操作

news2024/10/7 13:24:31

线程的基本操作

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

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

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

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

@Test
	public 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() {
    // 测试TimeUnit
    logger.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

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

@Test
	public 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 来处理中断异常,并在日志中打印一条相应的错误消息。

	@Test
	public 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();

		// 主线程休眠2s
		try {
			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()方法中断正常运行的线程
	 */
	@Test
	public 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();

		// 主线程休眠2s
		try {
			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() 方法可以实现多个线程之间的同步与协作,并保证主程序在满足特定条件后能够正确执行。
	 */
	@Test
	public void test5() {
		// 在调用join方法前确保被调用的线程已经启动,否则可能导致不可预测结果。
		// 在使用带超时参数的join方法时要处理InterruptedException异常。
		// 在使用多个thread.join()组合等待不同子任务时要考虑其顺序和逻辑合理性。
		Thread t1 = new Thread(() -> {
			logger.info("线程1开始执行了~");
			// 线程1 休眠5s
			try {
				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()方法

	@Test
	public 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.coloradmin.cn/o/1509351.html

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

相关文章

科技云报道:两会热议的数据要素,如何拥抱新技术?

科技云报道原创。 今年全国两会上&#xff0c;“数字经济”再次成为的热点话题。 2024年政府工作报告提到&#xff1a;要健全数据基础制度&#xff0c;大力推动数据开发开放和流通使用&#xff1b;适度超前建设数字基础设施&#xff0c;加快形成全国一体化算力体系&#xff1…

【Flutter】报错Target of URI doesn‘t exist ‘package:flutter/material.dart‘

运行别人项目 包无法导入报错&#xff1a;Target of URI doesn’t exist ‘package:flutter/material.dart’ 解决方法 flutter packages get成功 不会报错

Centos本地、公网邮件发送配置

目录 本地邮件发送 发送邮件的三种方式 接受邮件 配置公网发送邮件 发送文件 本地邮件发送 安装服务 # yum -y install postfix # yum -y install mailx 启动服务 # systemctl start postfix 发送邮件的三种方式 一. # mail-s“邮件主题” 收件人 ​ 邮件内容…

Linux - 安装 Jenkins(详细教程)

目录 前言一、简介二、安装前准备三、下载与安装四、配置镜像地址五、启动与关闭六、常用插件的安装 前言 虽然说网上有很多关于 Jenkins 安装的教程&#xff0c;但是大部分都不够详细&#xff0c;或者是需要搭配 docker 或者 k8s 等进行安装&#xff0c;对于新手小白而已&…

智谱清华LongAlign发布:重塑NLP长文本处理

引言 随着大型语言模型&#xff08;LLMs&#xff09;的不断进化&#xff0c;我们现在能够处理的文本长度已经达到了前所未有的规模——从最初的几百个tokens到现在的128k tokens&#xff0c;相当于一本300页的书。这一进步为语义信息的提供、错误率的减少以及用户体验的提升打…

解决方案RuntimeError: CUDA out of memory

文章目录 一、现象&#xff1a;二、解决方案 一、现象&#xff1a; PyTorch深度学习框架&#xff0c;运行bert-mini&#xff0c;本地环境是torch1.4-gpu&#xff0c;发现报错显示&#xff1a;RuntimeError: CUDA out of memory. Tried to allocate 224.00 MiB (GPU 0; 15.89 G…

保护物联网设备免受网络攻击的方法

可以肯定地说&#xff0c;物联网设备让我们的生活变得更加轻松和方便。这项新技术改变了人们在办公室工作的方式&#xff0c;也改变了他们在家里使用小工具的方式。办公室或家里的所有智能设备都可以连接&#xff0c;这让生活变得更加轻松。然而&#xff0c;这也使这些设备面临…

【Redis学习_可视化客户端连接Redis】

Redis学习_可视化客户端连接Redis Redis学习_可视化客户端连接Redis1、Another Redis Desktop Manager介绍2、Another Redis Desktop Manager连接 Redis学习_可视化客户端连接Redis 1、Another Redis Desktop Manager介绍 介绍 Another Redis Desktop Manager 支持哨兵, 集群,…

PIES源码,C#源码,大型体检中心源码,医院智慧体检系统源码

大型体检中心平台源码&#xff0c;医院智慧体检系统源码&#xff0c;PIES源码 C#源码 医院智慧体检系统&#xff0c;该系统覆盖医院、体检中心的所有业务&#xff0c;完成从预约、登记、收费、检查、检验、出报告、分析、报表等所有工作。系统可以对团检的每个环节设有操作界面…

javaEE7

1. <% page pageEncoding"UTF-8"%> <% page import"java.io.*"%> <% page import"java.util.*"%> <% page import"java.math.*"%> <html> <head><title>网站计数器</title>…

导出微软浏览器收藏的网页,并查看网页保存的登录密码

导出微软Edge浏览器收藏夹&#xff08;书签&#xff09;的步骤如下&#xff1a; 打开Microsoft Edge浏览器。右键点击浏览器收藏栏上的任意位置或使用快捷键Ctrl Shift O打开收藏夹管理页面。在收藏夹管理页面中&#xff0c;通常你会看到右上角或菜单区域有一个“…”或者三…

Java 使用 EasyExcel 实现导入导出(新手篇教程)

官网镇楼↓&#xff0c;觉得我写的不好的同学可以去官网看哦 EasyExcel Maven <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.3</version> </dependency> Excel 导入 示例&…

C++ STL --stack 和queue,priority_queue

1. stack的介绍和使用 1.1 stack的介绍 https://cplusplus.com/reference/stack/stack/?kwstack 翻译: 1. stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。 2. stack是作为容器适配…

Arm MMU深度解读

文章目录 一、MMU概念介绍二、虚拟地址空间和物理地址空间2.1、(虚拟/物理)地址空间的范围2.2、物理地址空间有效位(范围) 三、Translation regimes四、地址翻译/几级页表&#xff1f;4.1、思考&#xff1a;页表到底有几级&#xff1f;4.2、以4KB granule为例&#xff0c;页表的…

java SSM农产品订购网站系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM农产品订购网站系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采…

[云原生] K8s之ingress

1.Ingress的相关知识 1.1 Ingress的简介 service的作用体现在两个方面&#xff0c;对集群内部&#xff0c;它不断跟踪pod的变化&#xff0c;更新endpoint中对应pod的对象&#xff0c;提供了ip不断变化的pod的服务发现机制&#xff1b;对集群外部&#xff0c;他类似负载均衡器…

STM32 学习11 独立看门狗与窗口看门狗

STM32 学习11 独立看门狗与窗口看门狗 一、**看门狗概述**二、**STM32中的看门狗类型**1. 独立看门狗&#xff08;IWDG&#xff09;&#xff08;1&#xff09;基本概念&#xff08;2&#xff09;独立看门狗框图&#xff08;3&#xff09;独立看门狗配置步骤&#xff08;4&#…

基于Java+SpringBoot+vue+element实现汽车订票管理平台详细设计和实现

基于JavaSpringBootvueelement实现汽车订票管理平台详细设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 …

Django用户访问日志记录插件[django_user_visit 2.0]汉化

Django用户访问日志记录插件&#xff1a; django_user_visit汉化包 为了给用户显示中文&#xff0c;制作的汉化包&#xff0c;与原来版本区别仅仅是字符串 1&#xff1a;用户不同设备登录时&#xff0c;将会记录 2&#xff1a;用户与之前的ip不相同时&#xff0c;将会记录 3&am…

每日OJ题_路径dp⑥_力扣174. 地下城游戏

目录 力扣174. 地下城游戏 解析代码 力扣174. 地下城游戏 174. 地下城游戏 难度 困难 恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里&#xff0c;他必须穿过地下城并通过对…