多线程之——深入理解Thread构造函数

1.线程的默认命名

打开JDK的源码可以看到我们构造Thread的时候,默认的线程的名字是以Thread-开头,从0开始计数:即Thread-0、Thread-1 。。。。

 /**
     * Allocates a new {@code Thread} object. This constructor has the same
     * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
     * {@code (null, null, gname)}, where {@code gname} is a newly generated
     * name. Automatically generated names are of the form
     * {@code "Thread-"+}n, where n is an integer.
     */
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    /* For autonumbering anonymous threads. */
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

想修改线程的名字要趁早,在线程启动之前,还有一次修改线程名字的机会,一旦线程启动,名字就不能修改。

下面是在Thread中修改名字的代码:

public final synchronized void setName(String name) {
        checkAccess();
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
        if (threadStatus != 0) {
            setNativeName(name);
        }
    }

2.线程的父子关系

Thread的所有构造函数,最终都会去调用一个静态方法init,我们截取片段代码对其进行分析,不难发现新创建的任何一个线程都会有一个父线程:

这里截取Thread的init的部分代码:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();    //获取当前运行的线程作为父线程
        SecurityManager security = System.getSecurityManager();
        

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

上面代码中的 currentThread() 是获取当前线程,在线程生命周期中,我们说过线程的最初状态为NEW,没有执行 start 方法之前,它只能算是一个 Thread 的实例,并不意味着一个新的线程被创建,因此 currentThread() 代表的将会是创建它的那个线程,因此我们可以得出以下结论。

  • 一个线程的创建肯定是由另一个线程完成的。
  • 被创建线程的父线程是创建它的线程。

我们都知道 main 函数所在的线程是由 JVM 创建的,也就是 main 线程,那就意味着我们前面创建的所有线程,其父线程都是 main 线程。

3.Thread和ThreadGroup

在Thread的构造函数中,可以显示的指定线程的Group,也就是ThreadGroup,下面看init方法的中间部分:

        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

源码的意思: 如果在构造Thread的时候没有显示的指定一个ThreadGroup,那么子线程将会被加入父线程所在的线程组。

public static void main(String[] args) {
		PrintStream out=System.out;
		Thread t1=new Thread("t1");
		ThreadGroup group=new ThreadGroup("group1");
		Thread t2=new Thread(group,"t2");
		ThreadGroup mainGroup=Thread.currentThread().getThreadGroup();
		
		out.println("Main Thread Group:"+mainGroup.getName());
		out.println(t1.getThreadGroup()==mainGroup);
		out.println(t2.getThreadGroup().getName());
		out.println(t2.getThreadGroup()==group);
	}

输出

Main Thread Group:main
true
group1
true

得出结论:

  • main线程所在的ThreadGroup称为main;
  • 构造一个线程的时候如果没有显示的指定ThreadGroup,那么它将会和父线程属于同一个ThreadGroup(且拥有同样的优先级);

4.守护线程

基本性质。

  • 在正常的情况下,如果JVM没有一个非守护线程,JVM的进程才会退出。(当只有Daemon线程运行的时候才会退出);
  • setDaemon()方法必须在start()方法之前调用(否则会抛出IllegalThreadException);

线程是否为守护线程和它的父线程有很大的关系,如果父线程是正常线程,则子线程也是正常线程,反之亦然,如果你想要修改它的特性则可以借助 setDaemon 方法。isDaemon() 方法可以判断该线程是不是守护线程。

看个例子:

public static void main(String[] args) throws InterruptedException {
		Thread t=new Thread(()->{
			while (true) {
				System.out.println("t running....");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
		//t.setDaemon(true);
		System.out.println(t.isDaemon());
		t.start();
		Thread.sleep(2000);
		System.out.println("Main 线程结束");
	}

守护线程的作用

上面注释了t.setDaemon(true);运行结果如下,发现main 线程结束了,但是里面的线程没有结束。
多线程之——深入理解Thread构造函数_第1张图片
可以看到,即使main结束了,t线程仍在执行

但是如果不注释t.setDaemon(true);,当main线程结束,里面的线程就会结束。

多线程之——深入理解Thread构造函数_第2张图片
如果一个 JVM 进程中没有一个非守护线程,那么 JVM 会退出,也就是说守护线程具备自动结束生命周期的特性,而非守护线程则不具备这个特点, 试想一下如果 JVM 进程的垃圾回收线程是非守护线程,如果 main 线程完成了工作,则 JVM 无法退出,因为垃圾回收线程还在正常的工作。再比如有一个简单的游戏程序,其中有一个线程正在与服务器不断地交互以获取玩家最新的金币、武器信息,若希望在退出游戏客户端的时候,这些数据同步的工作也能够立即结束,等等。

守护线程经常用作与执行一些后台任务,因此有时它也被称为后台线程,当你希望关闭某些线程的时候,或者退出 JVM 进程的时候,一些线程能够自动关闭,此时就可以考虑用守护线程(setDaemon())为你完成这样的工作。

你可能感兴趣的:(并发——多线程)