【Java并发】守护线程与setDaemon()

在这里插入图片描述

前言

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方法设置线线程,则线程的模式是取决于父线程是否为守护线程,也就是创建此线程所在的线程是否为守护线程。

  1. 如果父线程是守护线程,创建的线程默认是守护线程;
  2. 如果父线程是用户线程,创建的线程默认是用户线程。

守护线程实现-运行

运行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守护线程是一种辅助性的线程,主要用于提供后台服务和维护工作,其生命周期取决于是否有非守护线程在运行。在设计和使用守护线程时,需要注意确保它们不会妨碍程序的正常关闭和资源释放。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

锥栗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值