线程的基本操作
Java线程的常用操作都定义在Thread类中,包括一些重要的静态方法 和线程的实例方法 。下面我们来学习一下,线程的常用基本操作
1.线程名称的设置和获取
- 线程名称可以通过构造
Thread
的时候进行设置,也可以通过实例的方法setName()
进行设置- 线程名称一般在启动线程前进行设置,但是Java也允许在运行时设置线程名称
- Java中允许两个线程具有相同的名称,但是一般不建议这么做。
- 如果创建线程时没有指定名称,那么会自动分配线程名称 一般是 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();
}
从输出的结果来看,如果没有进行手动赋值线程名称,那么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
枚举定义了一些常用的时间单位,包括:
- NANOSECONDS: 纳秒
- MICROSECONDS: 微秒
- MILLISECONDS: 毫秒
- SECONDS: 秒
- MINUTES: 分钟
- HOURS: 小时
- 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();
}
}
- 通过
TimeUnit.SECONDS.toMillis(1)
将1秒转换为毫秒,并使用日志输出这个转换结果。 - 使用
TimeUnit.MINUTES.toSeconds(1)
将1分钟转换为秒,并输出结果。 - 使用
TimeUnit.HOURS.toMinutes(1)
将1小时转换为分钟,并输出结果。 - 使用
TimeUnit.DAYS.toHours(1)
将1天转换为小时,并输出结果。 - 使用
TimeUnit.DAYS.toDays(7)
将1周转换为天,并输出结果。 - 使用了
TimeUnit.SECONDS.sleep()
方法,当前线程睡眠1秒,然后再次记录当前时间。在这个过程中,如果线程在睡眠期间被中断,它会抛出InterruptedException
异常,所以使用了try-catch
块来捕获可能抛出的异常,并在捕获到异常时打印出栈信息。
InterruptedException
sleep方法会有方法有可能抛出InterruptedException
异常。当当前线程在sleep()
期间被另一个线程调用了interrupt()
方法时,就会抛出InterruptedException
异常。
以下是关于InterruptedException
异常的一些要点:
- 触发条件:
InterruptedException
异常通常在调用Thread.sleep()
的线程被中断时抛出。这个中断是由其他线程通过调用interrupt()
方法引起的。 - 处理方式:通常,在捕获到
InterruptedException
异常时,你需要决定如何处理中断。可能的处理方式包括恢复线程的中断状态
,终止线程
,或者采取其他的线程中断处理逻辑
。 - 恢复中断状态:如果你在
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);
}
从运行结果,我们可以观察到一个线程,虽然 线程2
和 线程3
,线程3先开始执行休眠操作,但是 当休眠结束,线程3并没有立即得到执行,因为此时线程2获取到CPU时间片了。
由此,我们可以得出一个结论,当线程睡眠时间满后,线程不一定立即得到执行,因为此时CPU可能正在执行其他任务,此时的线程首先是进入就绪状态,等待CPU分配时间片便有机会执行(说白了,就是需要重新竞争获取CPU时间片)
3.interrupt操作
在Java中,
stop()
方法是一种已被废弃的方法,它用于停止线程的执行。然而,由于该方法可能会导致线程不可预测的状态和资源泄漏,不推荐使用。相反,Java提供了一种更安全和可控的方法来停止线程,即使用
interrupt()
方法。interrupt()
方法是Thread
类的一个实例方法,用于请求中断线程的执行。当调用线程的interrupt()
方法时,会设置线程的中断标志,然后根据线程的状态来决定如何响应中断请求。以下是一些关于
interrupt()
方法一些介绍
中断标志(Interrupt Flag):每个线程都有一个中断标志,它表示线程是否被请求中断。中断标志的初始值为
false
。当调用线程的interrupt()
方法时,中断标志会被设置为true
。中断异常(Interrupted Exception):某些阻塞操作(如
sleep()
、wait()
、join()
等)会在线程被中断时抛出InterruptedException
异常。这样一来,线程在执行这些阻塞操作时,可以通过捕获InterruptedException
来检查中断状态,并做出相应的响应。检查中断状态:线程可以通过调用
Thread
类的isInterrupted()
方法来检查自己的中断状态。这个方法返回一个布尔值,指示线程的中断状态。中断响应:线程可以根据中断状态选择不同的响应方式。常见的响应方式包括终止线程、清理资源、抛出异常等。线程可以在执行过程中定期检查中断状态,以决定是否终止执行。
不会立即打断正在运行的线程:当线程正在运行中时,调用
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();
}
打断正常运行的线程
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();
}
4.join
在Java中线程的合并,是一个比较难理解的概念。那什么是线程合并呢?在Java中,线程合并(Thread Join)是一种线程间的协作机制,它允许一个线程等待另一个线程执行完成后再继续执行。通过线程合并,可以有效地控制多个线程的执行顺序和并发性。
Java的Thread类提供了join()方法来实现线程的合并。
当一个线程调用另一个线程的join()方法时,它会进入等待状态,直到被调用join()方法的线程执行完毕
。在这种等待过程
中,当前线程会阻塞
,不会继续执行其他操作,直到被等待的线程执行完成或指定的超时时间到达。join()方法有三种重载形式:
public final void join() throws InterruptedException
: 无参方法,让当前线程等待被调用线程执行完成。
// 无参数的join其实在内部 调用了第二种方式 只是赋值了一个0 public final void join() throws InterruptedException { join(0); }
public final synchronized void join(long millis) throws InterruptedException
: 带超时参数的方法,让当前线程等待被调用线程执行完成,最多等待指定的时间(以毫秒为单位)。
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);
}
}
首先,创建了两个线程 t1
和 t2
。在 t1
线程中,通过 Thread.sleep()
方法模拟一个耗时的操作,这里是休眠5秒。
在 t2
线程中,首先使用 t1.isAlive()
方法判断 t1
线程是否还存活。如果 t1
线程仍然存活,即尚未执行完毕,那么调用 t1.join()
方法来等待 t1
线程执行完毕。只有当 t1
线程执行完毕后,t2
线程才会继续执行,并输出 “线程2开始执行了~”。
在主线程中,通过调用 t2.join()
方法,主线程会等待 t2
线程执行完毕后才继续执行。这样可以保证主程序在满足特定条件(这里是等待 t2
线程执行完毕)后能够正确执行。
需要注意的是,在调用 join()
方法之前,需要确保被调用的线程已经启动,否则可能会导致不可预测的结果。
限时等待线程执行结束
其实这里限时的意思,就是 会在确定的时间内取等待线程执行,如果超过这个时间线程还是没有执行完毕,那么另外的线程也不会去等待你执行完毕。他们会在时间到期后,立即去获取CPU时间片,去执行任务。
这里的代码 主要就是针对于 join添加 等待时间,代码和上面还是保持一致
在使用带超时参数的
join()
方法时,需要处理InterruptedException
异常。如果使用多个join()
方法组合等待不同的子任务时,还需要考虑其顺序和逻辑的合理性。
// 让主线程等待 t2 执行完毕
try {
// 我们这里只等待2s 如果线程2没有执行完毕,那么主线程就会继续执行
t2.join(TimeUnit.SECONDS.toMillis(2));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
logger.error("主程序执行结束~");
5.yield
线程的yield(让步)操作的作用是:
让出当前正在执行的线程,让出CPU的执行权,使得CPU去执行其他线程
。当线程调用
yield()
方法时,它会暗示线程调度器将执行权交给其他具有相同优先级的线程。具体来说,yield()
方法的作用是让出当前线程的执行权,以便让其他具有相同优先级的线程有机会执行。
JVM层面
,线程调用yield()
方法后,线程的状态仍然是RUNNABLE
。RUNNABLE
状态表示线程是可运行的,并且已经获得了所有必需的资源,可以在任何时候被线程调度器选择执行。调用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();
}
这个案例,创建了两个线程 t1
和 t2
。每个线程都会循环打印一系列消息,并在每次循环后调用yield()
方法让出执行权。
当程序运行时,两个线程交替执行。由于调用了yield()
方法,每个线程在打印一条消息后会主动让出CPU执行权,给其他线程执行的机会。这样,两个线程会交替地执行打印操作,而不是一个线程连续执行完再执行另一个线程。
请注意,由于线程调度的具体行为依赖于操作系统和JVM的实现,因此实际的执行顺序可能会因系统环境的不同而有所差异。此案例仅用于说明yield()
方法的作用,以及它在多线程场景下可能产生的效果。
综合上面来看,yeild()方法有以下特点
yield()
方法是Thread
类中的一个静态方法,用于让出当前线程的执行权。- 调用
yield()
方法会主动让出CPU执行权,使得其他具有相同优先级的线程有机会执行。 yield()
方法的调用并不会将线程状态转变为"就绪"状态,而是保持为"运行"状态(RUNNABLE
)。(从JVM角度看,从操作系统角度看,就是 “运行状态” -> ”就绪状态“)yield()
方法只是向线程调度器发出一个提示,表示当前线程愿意让出CPU执行权,但并不保证其他线程会立即执行。- 具体的线程调度行为取决于操作系统和JVM的实现,可能因不同的操作系统和硬件环境而有所差异。
yield()
方法的使用应谨慎,过度使用可能导致线程之间的不合理竞争和不稳定性。yield()
方法主要用于改善多线程场景下的线程间公平性和资源利用率,避免某个线程长时间独占CPU执行时间片。yield()
方法的效果因系统环境和线程的优先级而异,不能完全控制线程的执行顺序和频率。
6.线程的daemon操作
在Java中,线程可以分为两种类型:用户线程(User Thread)和守护线程(Daemon Thread)。
- 用户线程(User Thread):
- 用户线程是最常见的线程类型。当Java虚拟机(JVM)启动时,默认情况下创建的线程就是用户线程。
- 用户线程的目的是执行应用程序的业务逻辑,它们不会影响JVM的退出,即使所有的用户线程都执行完毕,JVM仍然会继续运行,直到所有的守护线程也执行完毕或被中止。
- 用户线程的执行结果不会影响JVM的终止,因此它们具有较长的生命周期。
- 守护线程(Daemon Thread):
- 守护线程是一种特殊类型的线程,在启动之前需要通过
setDaemon(true)
方法将线程设置为守护线程。- 守护线程的目的是为其他线程提供服务支持,它们通常用于执行一些后台任务或周期性的维护工作。
- 当所有的用户线程执行完毕后,JVM会检查是否只剩下守护线程在运行。如果只剩下守护线程,JVM会终止守护线程并退出。
- 守护线程的生命周期依赖于用户线程的存在。当所有的用户线程执行完毕后,守护线程会随之被中止。
守护线程的基本操作
在Thread类中,有一个实例属性和两个方法,是专门用于对守护线程进行相关操作
- 实例属性
daemon
:daemon
是Thread
类的一个实例属性,用于标识线程是否为守护线程。它是一个布尔值,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秒后结束,守护线程也随之中止。
守护线程和用户线程的关系
守护线程和用户线程的关系,有以下几个要点:
- 守护线程的创建和设置:可以通过
Thread
类的构造函数或使用setDaemon(true)
方法将线程设置为守护线程。- 守护线程的特点:
- 守护线程的优先级通常较低,当资源竞争时,用户线程更有可能获得CPU的执行时间片。
- 守护线程不能持有任何会导致程序不正常终止的资源,例如打开的文件或数据库连接。
- 守护线程的代码应该谨慎处理异常,因为它们往往无法捕获到由于JVM退出而导致的未捕获异常。
- 用户线程的特点:
- 用户线程的生命周期不受其他线程的影响,即使用户线程执行完毕,JVM仍然会继续运行直到所有的守护线程也结束。
总结来说,
守护线程是为用户线程提供支持和服务的线程,其生命周期依赖于用户线程
。当所有的用户线程执行完毕后,守护线程会随之中止。用户线程的生命周期不受其他线程的影响,即使用户线程执行完毕,JVM仍然会继续运行直到所有的守护线程也结束。\
守护线程注意事项
- 守护线程的作用:守护线程通常用于在程序运行时提供一种服务或者后台支持的功能。它们不应该执行一些需要确保完整性和稳定性的任务。
- 生命周期与非守护线程的区别:当所有非守护线程结束时,Java虚拟机会自动退出,而不管是否有守护线程仍在运行。这意味着守护线程的生命周期不应该影响整个程序的生命周期。
- 守护线程和用户线程之间的区别:
- 当仅剩下守护线程运行时,Java虚拟机会退出。
- 守护线程通常用于执行一些支持性任务,比如垃圾回收器线程就是一个守护线程。
- 注意事项:
- 守护线程不能用于执行文件I/O或者数据库操作等需要资源清理或状态管理的任务,因为在Java虚拟机退出时,这些操作可能无法完成,导致数据丢失或不一致。
- 守护线程应该谨慎地使用,确保不会在应用程序退出时导致数据丢失或者应用程序状态不一致的情况发生。-
*守护线程创建的也是守护线程
在Java中,当创建一个新线程时,如果该线程是通过守护线程创建的,那么它也将被设置为守护线程。换句话说,子线程的属性会继承自创建它的父线程。这意味着,如果一个线程是守护线程,那么它创建的任何子线程也将被设置为守护线程。
例如,如果您创建一个守护线程,并且在守护线程中启动了其他线程,那么这些新线程也将被视为守护线程,即使您没有明确地将它们设置为守护线程。
这种继承特性可以确保整个线程树的一致性,从而避免不必要的复杂性。