➡️博客首页 https://blog.csdn.net/Java_Yangxiaoyuan
欢迎优秀的你👍点赞、🗂️收藏、加❤️关注哦。
本文章CSDN首发,欢迎转载,要注明出处哦!
先感谢优秀的你能认真的看完本文,有问题欢迎评论区交流,都会认真回复!
守护线程、普通线程、两者之间的区别
- 一、✅典型解析
- 1.1 ✅什么是守护线程(概念)
- 1.2 ✅守护线程会阻塞其他线程吗
- 1.3 ✅守护线程有哪些优缺点
- 1.4 ✅ 哪些场景下需要使用守护线程
- 二、✅普通线程
- 2.1 ✅什么是普通线程(概念)
- 2.2 ✅如何创建线程
- 2.3 ✅如何开启线程
- 2.4 ✅如何终止线程
- 2.5 ✅线程的同步和通信是什么
一、✅典型解析
在Java中有两类线程: User Thread(用户线程)
、Daemon Thread(守护线程)
。用户线程一般用于执行用户级任务,而守护线程也就是 “ 后台线程 ”,一般用来执行后台任务,守护线程最典型的应用就是GC(垃圾回收器)。
这两种线程其实是没有什么区别的,唯一的区别就是Java虚拟机在所有<用户线程>都结束后就会退出,而不会等<守护线程>执行完。
1.1 ✅什么是守护线程(概念)
守护线程(Daemon Thread)
是一种在后台提供服务的线程,它在程序运行时在后台提供一种通用服务。守护线程并不属于程序中不可或缺的部分,它的生死与整个进程的运行无关。
守护线程的主要作用是为其他线程提供服务,例如垃圾回收线程就是典型的守护线程。当所有的用户线程都已退出运行时,如果还有守护线程存在,JVM(Java虚拟机)
会继续运行直到所有的守护线程也结束。当所有非守护线程结束时,如果没有守护线程存在,程序就会终止。
用户可以通过调用Thread类的 setDaemon(true)
方法将线程设置为守护线程模式,这样该线程就会在后台运行并一直服务其他线程。需要注意的是,守护线程不应该执行重要的操作,因为它的终止是不可控制的。
1.2 ✅守护线程会阻塞其他线程吗
守护线程在主线程退出时候会随主线程一起结束,而不会阻塞主线程的退出。
当我们在Java中创建一个线程时,我们可以通过调用Thread类
的setDaemon(true)
方法将其设置为守护线程。守护线程是在后台运行的,当没有用户线程在运行时,守护线程会自动终止。下面是一个示例。
/**
* @author xinbaobaba
* 如何创建一个守护线程
*/
public class DaemonThreadExample {
public static void main(String[] args) {
// 创建一个守护线程
Thread daemonThread = new Thread(() -> {
while (true) {
// 守护线程的逻辑
System.out.println("Daemon thread is running.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
daemonThread.setDaemon(true); // 设置守护线程
daemonThread.start(); // 启动守护线程
// 主线程逻辑
System.out.println("Main thread is running.");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread is exiting.");
}
}
代码解析:创建了一个守护线程和一个主线程。主线程会先运行并输出一条消息,然后休眠5秒钟。而守护线程会一直循环输出消息,直到主线程退出。由于守护线程是后台运行的,所以当主线程退出时,守护线程也会自动终止。
1.3 ✅守护线程有哪些优缺点
守护线程的优点:
- 守护线程在后台提供通用服务,可以减轻主线程的负担,使其专注于执行主要任务。
- 守护线程的创建和销毁成本相对较低,可以提高程序的启动速度和执行效率。
- 守护线程可以自动终止,不需要手动管理,减少了程序退出时的资源清理工作。
守护线程缺点:
- 守护线程的生命周期依赖于用户线程,如果用户线程全部退出,守护线程也会被终止,可能导致一些资源无法得到正确的清理。
- 守护线程不控制资源,这可能导致一些重要的资源在程序退出时仍被占用,影响程序的正确关闭。
- 守护线程的执行优先级较低,可能会影响程序的实时性和响应速度。
因此,在使用守护线程时需要注意权衡利弊,根据具体需求选择是否使用以及如何使用。
1.4 ✅ 哪些场景下需要使用守护线程
守护线程适用于以下场景:
- 后台任务:有些任务需要在程序运行的后台执行,而不需要与主线程同步。比如,日志记录、垃圾回收、定时任务等都可以使用守护线程来执行,减少对主线程的干扰。
- 定时任务:一些定时任务可以作为守护线程执行,例如定时备份数据库、定时清理临时文件等。
- 服务监听:在服务器应用中,可以使用守护线程监听某个端口,等待客户端的连接请求。
- 资源管理:守护线程还可以用于资源管理,例如数据库连接池中的线程池管理器可以使用守护线程来监控空闲连接并进行回收。
- 程序退出:当所有的非守护线程都结束时,守护线程会自动终止。这在一些特定的应用场景下非常有用,比如服务器端应用,在所有客户端连接都断开后,守护线程可以自动关闭服务。
注意:守护线程并不适合执行需要持续运行的任务,因为它们会在所有用户线程结束后自动退出。在实际开发中,我们可以根据具体需求来合理使用守护线程,提高程序的性能和可靠性。
二、✅普通线程
2.1 ✅什么是普通线程(概念)
普通线程是用户创建的一般线程,具有个体性,不具有提供公共服务的性质。通常需要我们在线程的循环语句中手动编写循环结束语句,也即线程运行终止的条件语句。
普通线程与守护线程不同,它在程序运行过程中一直存在,直到程序结束或手动终止。普通线程可以与其他线程并发执行,并在其生命周期内完成特定的任务。
在Java中,可以通过继承Thread类或实现Runnable接口来创建普通线程。普通线程的创建和管理需要更多的资源,因此需要注意线程的启动、同步、通信和异常处理等问题。
总结起来的话,普通线程是用户创建的常规线程,需要手动管理其生命周期,常用于执行需要持续运行的任务或并发操作。
2.2 ✅如何创建线程
创建线程的方法主要有两种:继承Thread类和实现Runnable接口。
- 继承Thread类:创建一个线程类,继承Thread类,并重写run()方法。在run()方法中编写线程要执行的代码。创建线程对象时调用其构造函数,启动线程时调用start()方法。例如:
/**
* @author xinbaobaba
*/
public class MyThread extends Thread {
public void run() {
// 线程执行的代码
}
}
MyThread thread = new MyThread();
thread.start();
- 实现Runnable接口:创建一个实现了Runnable接口的类,实现run()方法,在run()方法中编写线程要执行的代码。创建一个Thread对象,将实现Runnable接口的类的对象作为参数传递给Thread构造函数,调用Thread对象的start()方法启动线程。例如:
/**
* @author xinbaobaba
*/
public class MyRunnable implements Runnable {
public void run() {
// 线程执行的代码
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
无论使用哪种方法创建线程,都需要调用start()方法
启动线程,而不是直接调用run()方法
。因为run()方法
是普通方法,直接调用会立即执行,不会创建新的线程。而start()方法
会启动一个新的线程来执行run()方法
中的代码。
2.3 ✅如何开启线程
要开启线程,需要创建一个线程对象并调用其start()方法。
在Java中,可以通过继承Thread类或实现Runnable接口来创建线程。无论使用哪种方法,都需要实现run()方法,并在其中编写线程要执行的代码。
创建线程对象时,可以直接使用Thread类或通过实现Runnable接口的类来创建。然后,调用线程对象的start()方法来启动线程。
例如,以下是一个简单的Java代码示例,演示如何开启线程:
/**
* @author xinbaobaba
*/
public class MyThread extends Thread {
public void run() {
// 线程执行的代码
System.out.println("Thread is running.");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
代码解析:创建了一个继承Thread类的MyThread类,并实现了run()方法。然后,在主函数中创建了一个MyThread对象,并调用其start()方法来启动线程。当线程启动后,它将执行run()方法中的代码,输出“Thread is running.”。
2.4 ✅如何终止线程
终止线程的方法主要有三种:
- 使用退出标志:在run()方法中设置一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。
- 使用stop方法:强制终止线程,但这种方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果。
- 使用interrupt方法:中断线程。可以通过捕获InterruptedException异常来控制线程的退出。
注意,终止线程时应该谨慎处理,避免资源泄漏和数据不一致等问题。在实际开发中,应该根据具体情况选择合适的方法来管理线程的生命周期。
在Java中,可以通过设置一个boolean类型的变量作为线程的退出标志,在线程的while循环中不断检查这个标志,如果标志为true,则退出循环并结束线程。以下是一个示例代码:
/**
* @author xinbaobaba
*/
public class MyThread extends Thread {
private boolean isRunning = true;
public void run() {
while (isRunning) {
// 线程执行的代码
System.out.println("Thread is running.");
}
}
public void stopThread() {
isRunning = false;
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
// 等待一段时间后终止线程
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.stopThread(); // 停止线程
}
}
代码解析:创建一个继承Thread类的MyThread类,并在其中定义了一个boolean类型的变量isRunning作为线程的退出标志。在run()方法中,我们使用while循环不断检查isRunning变量的值,如果
isRunning为true
,则执行线程要执行的代码。当需要停止线程时,可以在其他地方调用stopThread()方法
将isRunning
设置为false,从而结束while循环并退出线程。
在主函数中,我们创建了一个MyThread对象,并调用其start()方法来启动线程。然后,我们使用Thread类的sleep()方法
使主线程休眠5秒钟,模拟一段时间后需要停止线程的场景。最后,我们调用thread对象的stopThread()
方法来停止线程。需要注意的是,在实际开发中,应该避免使用Thread类的stop()方法来停止线程,因为该方法会强制终止线程,可能导致资源泄漏和数据不一致等问题。
问:线程的三种终止方法,哪一种最常用呢 ?
线程的三种终止方法包括使用退出标志、使用stop方法和使用interrupt方法。其中,最常用的方法是使用interrupt方法
。
使用 interrupt方法
来终止线程是最常用的方法,因为它提供了一种优雅的方式来通知被停止的线程停止执行。通过调用线程的interrupt方法,可以向线程发送一个中断请求,线程可以选择在适当的时候响应这个请求并停止执行。这种方式的优点在于它不会立即强制停止线程,而是给线程一个机会来清理资源、释放锁等,从而避免出现资源泄漏或其他问题。此外,interrupt方法还提供了一种通用的机制,可以用于处理其他类型的中断请求,如用户界面中的取消操作等。
相比之下,使用退出标志或stop方法来终止线程并不常用。使用退出标志需要在程序中设置一个标志变量,当需要停止线程时将标志变量设置为true,然后等待线程自行停止。这种方式需要线程自行处理停止逻辑,而且可能会造成线程阻塞或死锁等问题。使用stop方法会强制停止线程,可能会导致资源泄漏或其他问题,因此不推荐使用。
综上所述,使用interrupt方法是线程终止最常用的方法,因为它提供了一种优雅、安全的方式来停止线程执行,并可以用于处理其他类型的中断请求。
2.5 ✅线程的同步和通信是什么
线程的同步和通信是实现多线程并发执行的必要机制。
线程同步是指线程之间的协调与合作,以确保它们能够按照正确的顺序执行,避免出现数据不一致或资源冲突的问题。线程同步可以通过互斥锁、条件变量、信号量等机制实现,以确保同一时刻只有一个线程访问共享资源。
线程通信是指线程之间传递数据或消息的机制。由于不同的线程可能执行在不同的处理器或不同的内存空间上,因此需要一种机制来在不同的线程之间传递信息。线程通信可以通过共享内存、消息队列、管道等机制实现,以确保线程之间能够正确地传递数据或状态信息。
线程的同步和通信是多线程编程中的重要概念,用于协调和管理不同线程的执行,以确保程序能够正确地运行并实现预期的功能。
Demo:
/**
* @author xinbaobaba
* 演示了线程同步和通信的基本概念
*/
public class SharedResource {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
return count;
}
}
public class MyThread extends Thread {
private SharedResource resource;
public MyThread(SharedResource resource) {
this.resource = resource;
}
public void run() {
for (int i = 0; i < 10; i++) {
resource.increment();
System.out.println("Thread " + Thread.currentThread().getId() + " count: " + resource.getCount());
}
}
}
public class Main {
public static void main(String[] args) {
SharedResource resource = new SharedResource();
MyThread thread1 = new MyThread(resource);
MyThread thread2 = new MyThread(resource);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + resource.getCount());
}
}
在上面的代码中,我们定义了一个SharedResource类,其中包含一个共享资源count和一个同步锁lock。increment()方法用于增加count的值,并在访问count时进行同步,以确保线程安全。MyThread类继承了Thread类,并在run()方法中模拟了对共享资源的并发访问。在Main类中,我们创建了两个MyThread对象,并启动它们。使用 join()
方法等待线程执行完成,然后输出最终的count值。
这个示例演示了线程同步的基本概念,通过synchronized关键字
对共享资源进行同步访问,确保同一时刻只有一个线程能够修改count
的值。同时,通过线程通信的方式,每个线程在访问完共享资源后输出count的值,以便观察线程的执行顺序和共享资源的状态变化。