Java多线程(二)——守护线程,以及Java线程的“父子关系”

Posted by Lain on 09-30,2019

0
上一篇文章里,我们知道了,当线程对象被创建时如果不指定线程组,构造器会默认指定当前线程所在的线程组作为新线程的线程组,在线程组的层面上,子线程和父线程实际上是平级的,并没有实际上的区别。

守护线程

今天我们来看看守护线程。守护线程本身很简单,只要在线程start方法执行之前,为线程对象设置setDaemon(true),就可以将当前线程设置为守护线程。如果在线程执行之后再设置,将会抛出异常。
下面是相关源代码:

/**
     * 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();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }

可以看到,在为线程设置守护线程状态的时候,会先检查线程是否是存活状态(alive,如线程还未执行,则是Runnable),如果已经在运行了,则会抛出 IllegalThreadStateException 异常。
注意看注释,注释中说明了,当JVM中运行的所有线程全部都是守护线程的时候,JVM将会直接结束,反过来说,当只要还有一个用户线程存在,守护线程就会继续运行直到跑完,否则不管守护线程有没有执行完毕,都会直接结束。

但一开始,很多人也会有如下疑问:守护线程是否和创建它的父线程有关系?毕竟在逻辑上,在线程里创建线程,前者叫父线程,后者叫子线程,如果子线程是守护线程的话,是不是父线程结束了,子线程也跟着结束了呢?

实际上Java中的线程是不存在父子关系的,就如注释里说的那样,守护线程退出只有有两种情况:

  1. 线程本身执行完了
  2. JVM中没有用户线程了(非守护线程)

在线程中创建线程,除了会从“父线程”身上继承一些属性以外(比如线程组,守护线程标识,线程优先级等属性),两者在执行上是完全没有关系的。
下面是Thread的init方法中的片段:

	Thread parent = currentThread();//在创建线程的时候,以当前线程为parent
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
   	    //....
            if (g == null) {
                g = parent.getThreadGroup();//parent的线程组
            }
        }
	//....
        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();//parent的守护线程标识
        this.priority = parent.getPriority();//parent的优先级
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();//parent的上下文类加载器
        else
            this.contextClassLoader = parent.contextClassLoader;
	//.....

因此,无论在哪里创建线程,虽然逻辑上我们认为他们是父子关系,但还是要记住,他们只是继承了一些属性而已,并不会互相影响,并不存在实际上的父子关系。
下面随便写了一个测试用的程序(很容易证明,所以不用看下面这一堆的==):

package fun.lain.laintest.thread;

public class DaemonThreadTest {
    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        thread1.start();
    }
}
class Thread1 extends Thread{
    @Override
    public void run() {
        Thread2 thread2 = new Thread2();
        thread2.start();
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class Thread2 extends Thread{

    @Override
    public void run() {
        Thread3 thread3 = new Thread3();
        thread3.setDaemon(true);
        thread3.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("T2 Priority:" + this.getPriority());
        System.out.println("3s pass,T2 is end");
    }
}

class Thread3 extends Thread{
    @Override
    public void run() {
        try {
            System.out.println("T3 is running");
            System.out.println("T3 Priority:" + this.getPriority());
            Thread.sleep(5_000);
            System.out.println(this.isDaemon());
            System.out.println(Thread.currentThread().getThreadGroup().getName());
            Thread4 thread4 = new Thread4();
            thread4.start();
            Thread.sleep(1_500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("5s pass, T3 is end");
    }

}
class Thread4 extends Thread{
    @Override
    public void run() {
        try {
            System.out.println("T4 is running");
            Thread.sleep(1_000);
            System.out.println("T4:" + this.isDaemon());
            System.out.println(Thread.currentThread().getThreadGroup().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1s pass, T4 is end");
    }

}

运行结果:

T3 is running
T3 Priority:5
T2 Priority:5
3s pass,T2 is end
true
main
T4 is running
T4:true
main
1s pass, T4 is end
5s pass, T3 is end // T3是在T2中创建的守护线程,执行时间比T2长,在T2结束后依旧正常执行完毕,因此两者没有父子关系