Java多线程拾遗(二)——Thread构造函数

前言

上一篇博客中我们总结了多线程的几种状态,已经梳理了如何简单的构建多线程,本篇博客打算继续梳理Thread的几种构造函数和守护线程。

Thread的构造方法

打开Thread的源码,发现其有如下几个构造函数

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

Thread(Runnable target, AccessControlContext acc) {
    init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}

public Thread(ThreadGroup group, Runnable target) {
    init(group, target, "Thread-" + nextThreadNum(), 0);
}

public Thread(String name) {
    init(null, null, name, 0);
}

public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}

public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}

public Thread(ThreadGroup group, Runnable target, String name) {
    init(group, target, name, 0);
}

public Thread(ThreadGroup group, Runnable target, String name,
              long stackSize) {
    init(group, target, name, stackSize);
}

其实这些所有的构造函数,底层都会调用如下方法

//初始化一个线程,并设置线程组和线程id
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();
    if (g == null) {
        if (security != null) {
            g = security.getThreadGroup();
        }

        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    g.checkAccess();


    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);
    this.stackSize = stackSize;

    tid = nextThreadID();
}

其实这里面主要是完成一些线程id和线程组的设置,其中还有一些细节,后面会梳理到。

Thread的名称

"Thread-" + nextThreadNum()

init方法中有时候会传递这个方法,这个方法默认构造Thread的名称。

private static int threadInitNumber;//初始值为0
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

可以看出,线程默认的命名方式是以"Thread-"为前缀,然后加上一个自增的数字,这个自增的数字在整个JVM进程中将会不断自增。

利用如下构造函数,我们其实也可以自己定义一个线程的名称。

public Thread(String name) {
    init(null, null, name, 0);
}

不论我们使用默认的线程名称还是指定一个我们自定义的线程名称,在线程启动之前还可以修改线程的名称,一旦线程启动,我们就无法再修改线程的名称了。

Thread初始化时的ThreadGroup

//g是传递进来的ThreadGroup参数,这里会获取当前线程作为父线程。
Thread parent = currentThread();
//获取当前线程的安全管理程序
SecurityManager security = System.getSecurityManager();
if (g == null) {
    if (security != null) {
        g = security.getThreadGroup();
    }

    //如果在初始化线程的时候,没有指定线程组,则将当前线程加入到父线程的线程组中。
    if (g == null) {
        g = parent.getThreadGroup();
    }
}

可以看到,如果在初始化线程的时候,没有给初始化的线程设定线程组,则将当前线程加入到父线程的线程组中,那么什么是当前线程的父线程呢?
之前我们说过,在没有完成Init之前,这个时候的线程并没有启动,还是new状态,而在Init方法中currentThread获取的是当前线程,这里的当前线程指的是创建这个线程的线程。

简单来说,我们可以总结成如下几点:

1、一个线程的创建肯定是由另一个线程完成的

2、被创建线程的父线程是创建它的线程(例如,线程A在main中创建,则main线程就是这个线程的父线程)

我们知道main线程是由JVM创建的,我们在main函数中创建的线程其父线程都是main线程。

同时在上面的代码中我们还会看到,在线程init的时候没有显示的指定线程所在的ThreadGroup,则会将线程加入到父线程所在的线程组

参看如下实例:

/**
 * autor:liman
 * createtime:2020/6/6
 * comment: ThreadGroup的实例
 */
@Slf4j
public class InitThreadGroup {

    public static void main(String[] args) {
        Runnable r = ()->{
            try {
//                Thread.sleep(50000l);
            } catch (Exception e) {
                e.printStackTrace();
            }
            log.info("this is child thread");
        };

        ThreadGroup threadGroup = new ThreadGroup("slef thread group");
        ThreadGroup mainThreadGroup = Thread.currentThread().getThreadGroup();
        Thread thread = new Thread(threadGroup,r,"childThread");
//        Thread thread = new Thread(r);//如果打开这一行注释,最后一行输出会成为true
        thread.start();
        log.info("mainThread belongs thread group : {}",mainThreadGroup.getName());
        log.info("childThread belongs thread group : {}",threadGroup.getName());
        log.info("two thread group is same : {}",mainThreadGroup==thread.getThreadGroup());
    }

}

Thread与JVM虚拟机栈

上述在构造Thread的时候还有一个参数,stacksize,这个参数有什么作用呢,这个就和jvm的内容有关了

Thread与stacksize

很多情况下我们创建线程不需要指定stacksize,会通过-xss的虚拟机参数进行设置即可。关于stack size,JDK的官网中是这样介绍的

The stack size is the approximate number of bytes of address space that the virtual machine is to allocate for this thread's stack.The effect of the stackSize parameter,if any, is highly platform dependent.

这句话翻译过来就是stack size 是虚拟机分配给线程的虚拟机栈的大小,这个参数高度依赖于不同的系统平台。**stack size这个值越小,那么JVM可以创建的线程数量就越多。但是这个值越大,随之而来的函数调用的深度也就越大。**可以初略的认为,一个Java进程的大小=堆内存+线程数量*栈内存(stack size)

JVM内存模型

stacksize无需手动构建,但是在学习线程的过程中会发现,线程和栈内存的关系非常密切,在了解这两者关系之前需要熟悉JVM内存模型,这个之前总结过,这里不再赘述——传送门

守护线程

这个是一个比较特殊的线程,一般只是完成一些后台处理的工作,关于守护线程,其实JDK文档中有一个描述

The Java Virsual Machine exits when the only threads running are all daemon threads.

翻译过来就是:在正常情况下,如果JVM中没有一个非守护线程,则JVM的进程会退出。

来看一个实例

@Slf4j
public class DaemonThreadDemo {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
           while(true){
               try {
                   log.info("self thread start");
                   Thread.sleep(1);
                   log.info("self thread end");
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        //将我们自己的线程设置为守护线程
        thread.setDaemon(true);
        thread.start();
        log.info("Main thread finished");
    }
}

这个时候会发现,我们的线程并不能持久的运行下去,因为main线程结束之后,我们的进程中并没有非守护线程了,这个时候进程就会终止。运行结果如下。如果将thread.setDaemon(true)注释掉,则我们的线程会一直运行下去。

在这里插入图片描述

由于我们的main线程是一个非守护线程,如果将thread.setDaemon(true)注释掉,main线程结束之后,当前进程中再无守护线层,则该进程会终止。同时顺便总结一句,thread.setDaemon()的操作必须在线程start之前完成,否则会抛出异常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章