欢迎大家搜索“小猴子的技术笔记”关注我的公众号,有问题可以及时和我交流。

    Thread类是一个构建线程的关键类,通过传递一个实现了Runnable接口的类就可以简单构造出一个线程对象,下面就来看看有关Thread类的一些基础知识点吧(本文略长请耐心阅读,相信你一定受益匪浅)。
    Thread一共有8种(public修饰)构造函数和一种(default修饰)默认构造函数,分别如下所示:

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);
}

    通过观察以上的构造函数,其实不难发现,所有的构造方法最终都是调用了一个叫"init()"的方法。接下来我们就重点来关注下这个“init()”方法到底做了一些什么事,完成了哪些操作。

    通过观察源码可以发现“init()”方法也有两个重载,但是最终的调用还是参数比较多的那个方法。其实根据多阅读源码的经验,不难发现,把参数最多的一个方法封装好,可以根据方法的重载来实现不同参数的方法实现。然后我们重点来解读参数较多的“init()”方法。


private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
     init(g, target, name, stackSize, null, true);
}
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();
}

    乍一看上面的“init()”里面的内容确实有点多,请不要着急,静下心来跟着我的思路一点一点的阅读。


if (name == null) {
    throw new NullPointerException("name cannot be null");
}

    第一步,就是对线程名字进行一个判断,判断是不是等于空,如果传递的线程名字是空值的话,那就会抛出空指针异常来提醒用户。也许你一时间会诧异,为什么之前构造的线程没有传递名称,他也能正常运行呢?比如下面这样:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("没有构造线程名称");
    }
}
public class MyRunnableTest {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}

    不错,这样是可以正确运行的,也是能够输出“没有构造线程名称”这句话的。其实这个还要回到文章刚开始的地方,8个公共的构造方法的重载上。通过跟进上述例子中的源码不难发现,上述例子中走的构造方法是下面的这个:


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

    也许细心的你已经发现了,构造方法中会默认帮我们生成一个以"Thread-"开头整数结尾的线程名。因为“nextThreadNum()”使用了“synchronized”关键字修饰,所以他是线程安全的,不必担心多个线程会引起数字错乱的问题。

private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

    第二步,就是给线程赋予名字,这个名字如果你指定了一个名字,那线程名就是你指定的。如果没有指定,那就是构造函数给你默认生成的一个。

    我们都应该知道一个线程的构造和启动,肯定是由另一个线程来完成的。因此下面这个代码就是获取构造这个线程的父线程:

Thread parent = currentThread();

    而“currentThread()”方法是一个本地方法,也就是交给底层去操作了。我们无须关系它到底实现了什么,只需要先知道他为我们拿到了构造这个线程的父线程就行了。


public static native Thread currentThread();

    第三步,判断我们传递的“ThreadGroup”是不是为空,目的就是显示的为线程对象设置一个线程组,如果没有显示的设置线程组(也就是“g == null”条件成立)则会去SecurityManager获得线程组(SecurityManager具体含义后面文章再讲)。如果还是没有获取到则设置为父线程所归属的线程组。

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;
}

    第八步,是设置该线程的继承的AccessControlContext(AccessControlContext用于根据其封装的上下文做出系统资源访问决策,具体的介绍可以查看API的介绍)。

this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();

    第九步,是设置与此线程有关的InheritableThreadLocal值,它由一个ThreadLocalMap保存(后期文章讲ThreadLocalMap会着重讲到它,这里可以简单理解为一个map)。

if (inheritThreadLocals && parent.inheritableThreadLocals != null){
    this.inheritableThreadLocals =  ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}

    第十步,是设置新线程的所需堆栈大小,如果你了解JVM Stack(虚拟机栈)那么这个栈的深度应该就很容易理解。如果不了解的话,就认为它开辟了一块内存空间即可。

this.stackSize = stackSize;

    第十一步,是设置线程的ID,每一个线程都有一个唯一的ID进行标识。可以看到线程ID的设置是调用了“nextThreadID()”而“nextThreadID()”也用了“synchronized ”关键字进行修饰,所以也是线程安全的

tid = nextThreadID()

private static long threadSeqNumber;
private static synchronized long nextThreadID() {
    return ++threadSeqNumber;
}

    给一个图希望加深印象(画图写作真的很累,希望能帮到你,顺便公众号关注我下哈_):
在这里插入图片描述

    到此为止,一个基本的线程对象就构建完成了,不过需要注意的是他还没有启动,只是简单的构造了一个线程对象。
    欢迎大家搜索“小猴子的技术笔记”关注我的公众号,有问题可以及时和我交流。


小猴子的技术笔记
15 声望1 粉丝