前言
Java的守护线程(Daemon Thread)是一种特殊类型的线程,它的主要目的是为其他线程提供服务或者执行一些后台维护任务。
守护线程无法独立存在,当进程中不存在非守护线程了,则守护线程自动销毁。
守护线程通常用于执行那些不是程序主体部分,但在程序运行过程中需要持续进行的任务,比如垃圾回收器就是一个典型的守护线程,它在后台进行内存管理,Java可以通过调用 Thread.setDaemon(true) 方法将一个线程设置为守护线程。这个方法必须在调用 start() 方法启动线程之前调用,否则会抛出 IllegalThreadStateException 异常。
主线程(即创建 JVM 时启动的线程)默认是非守护线程。如果主线程结束而仍有守护线程在运行,JVM也会退出。
注意:由于守护线程的存在是为了支持用户线程,所以它们不应该执行可能会阻止 JVM 退出的操作,比如无限循环或阻塞在某个I/O操作上。
通常情况下,守护线程不会被显式地中断或停止,而是随着所有非守护线程的终止而自然结束。但是也可以通过调用 Thread.stop()、Thread.interrupt() 等方法来强制终止一个守护线程,但通常不推荐,因为可能会导致资源泄露或其他未预期的行为。
守护线程实现
我们令主线程睡3秒,3秒后结束、
我们令t1线程死循环,但是这里我们将t1设置为守护线程,则当主线程结束后,t1线程也会结束。
有demo如下:
DaemonDemo.java
import java.util.concurrent.*;
public class DaemonDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 开始运行, " +
(Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
// t1线程死循环!
while (true) {
}
}, "t1");
// 【这句可注释】
// 如果t1不是守护线程,则t1与main独立,main结束t1还不会结束(还在while(true)里),
// 如果t1是守护线程,那么main主线程结束,t1也会结束
// setDaemon()方法必须在start()方法前设置,否则报错
t1.setDaemon(true);
t1.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t -----end 主线程");
}
}
注意:如果没有调用setDaemon方法设置线线程,则线程的模式是取决于父线程是否为守护线程,也就是创建此线程所在的线程是否为守护线程。
- 如果父线程是守护线程,创建的线程默认是守护线程;
- 如果父线程是用户线程,创建的线程默认是用户线程。
守护线程实现-运行
运行DaemonDemo.java如下
如果没有
t1.setDaemon(true);
那么t1线程应该会一直在while(true)中进行下去,但是由于设置了守护线程,所以这里main主线程结束之后,t1线程就结束了。
非守护线程
当作为非守护线程创建后:
代码如下:
package org.sample.juc;
import java.util.concurrent.*;
public class DaemonDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 开始运行, " +
(Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
// t1线程死循环!
while (true) {
}
}, "t1");
// 【这句可注释】
// 如果t1不是守护线程,则t1与main独立,main结束t1还不会结束(还在while(true)里),
// 如果t1是守护线程,那么main主线程结束,t1也会结束
// setDaemon()方法必须在start()方法前设置,否则报错
// t1.setDaemon(true);
t1.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t -----end 主线程");
}
}
非守护线程-运行
运行结果如下:
可以看到最后直接死循环了,t1这时候是一个独立线程,因此主线程结束t1还是继续运行。
守护线程源码
public class Thread implements Runnable {
// ...
private volatile boolean daemon = false;
/**
* Determines if the currently running thread has permission to
* modify this thread.
* <p>
* If there is a security manager, its {@code checkAccess} method
* is called with this thread as its argument. This may result in
* throwing a {@code SecurityException}.
*
* @throws SecurityException if the current thread is not allowed to
* access this thread.
* @see SecurityManager#checkAccess(Thread)
*/
public final void checkAccess() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkAccess(this);
}
}
/**
* Marks this thread as either a {@linkplain #isDaemon daemon} thread
* or a user thread. The Java Virtual Machine exits when the only
* threads running are all daemon threads.
*
* <p> This method must be invoked before the thread is started.
*
* @param on
* if {@code true}, marks this thread as a daemon thread
*
* @throws IllegalThreadStateException
* if this thread is {@linkplain #isAlive alive}
*
* @throws SecurityException
* if {@link #checkAccess} determines that the current
* thread cannot modify this thread
*/
public final void setDaemon(boolean on) {
checkAccess();
this.daemon = on;
if (on && threadStatus != 0)
throw new IllegalThreadStateException();
}
// ...
private native void start0();
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the {@code run} method of this thread.
* <p>
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* {@code start} method) and the other thread (which executes its
* {@code run} method).
* <p>
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @throws IllegalThreadStateException if the thread was already started.
* @see #run()
* @see #stop()
*/
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
// ...
private native void stop0(Object o);
public final void stop() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
}
if (threadStatus == 0) {
stop0(this);
return;
}
throw new ThreadDeath();
}
// ...
}
checkAccess()
方法:方法用于检查当前线程是否有权限访问(修改)这个线程。如果存在安全管理器(SecurityManager),则调用其 checkAccess() 方法,并将当前线程作为参数传递。这可能会抛出 SecurityException。
setDaemon(boolean on)
方法:方法用于设置线程是否为守护线程。如果 on 参数为 true,则将线程标记为守护线程;否则,标记为用户线程。
在设置之前,会先调用 checkAccess() 方法进行权限检查。
如果尝试在已经启动的线程上调用此方法(即 threadStatus != 0),则抛出 IllegalThreadStateException 异常。
start0() 方法:本地方法,由 JVM 实现,用于启动线程并执行其 run() 方法。
start()
方法:方法用于启动线程。
首先检查线程状态是否为 “NEW”(即尚未启动),如果不是,则抛出 IllegalThreadStateException 异常。
然后将线程添加到其所属的线程组中,并减少线程组中未启动的线程数。
调用本地方法 start0() 来启动线程。
如果线程启动失败,则调用 group.threadStartFailed(this) 方法通知线程组。
stop0(Object o)
方法:本地方法,由 JVM 实现,用于强制停止线程。
stop()
方法:用于强制停止线程。如果存在安全管理器,则检查当前线程是否有 STOP_THREAD_PERMISSION 权限,如果没有,则抛出 SecurityException。
如果线程尚未启动(即 threadStatus == 0),则调用本地方法 stop0(this) 来停止线程并直接返回。
否则,抛出 ThreadDeath 异常,通常会导致线程被中断。
总结
Java守护线程是一种辅助性的线程,主要用于提供后台服务和维护工作,其生命周期取决于是否有非守护线程在运行。在设计和使用守护线程时,需要注意确保它们不会妨碍程序的正常关闭和资源释放。