java多线程系列(3)线程构造方法(源码剖析)

简介: 在上一篇文章中对线程状态生命周期和常见的线程api进行了一个讲解。这篇文章开始着重对其构造方法进行一个说明,也将揭晓为什么我们调用了start方法就能启动一个线程。

一、守护线程和非守护线程


我们获取线程的id的时候会发现每次都不是0,这是因为在java虚拟机运行一个线程的时候会默认启动一些其他的线程,来为我们的线程服务。默认创建的和我们自己创建的线程是有区分的。这就要区分守护线程和非守护线程了。


1、什么是守护线程和非守护线程?


默认启动的这些线程就是守护线程,他专门处理一些后台的工作。比如说垃圾回收等。非守护线程就是我们自己创建的这些线程。官方文档指出,当java虚拟机中没有非守护线程了,默认线程也会退出。举个例子就能明白:

守护线程就像饭店里面的服务员,非守护线程就像是顾客,顾客没有了,那么服务员也没有存在的必要了。


2、代码演示


我们通过代码来演示一下他们的作用,

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true) {
                System.out.println("无限循环");
            }
        }) ;
        thread.start();
    }
}

在这里主要有两个线程一个是main线程,第二个就是自己创建的thread。运行之后很明显程序会无线的执行下去,因为thread是非守护线程。即使是main线程执行结束了thread也会执行。现在我们把thread设置为守护线程就不一样了。

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while (true) {
                System.out.println("无限循环");
            }
        }) ;
        //设置为守护线程
        thread.setDaemon(true);
        thread.start();
    }
}

在运行一遍,我们会发现程序正常的退出了,这是因为我们把thread设置成了守护线程,你想想看main线程和thread都变成了服务员,现在没有顾客了,于是这些守护线程到店里转悠一圈就走了。


3、守护线程应用场景?


你了解了守护线程的特点之后,就可以运用这个原理做一些意想不到的事,比如说在退出jvm的时候也想让一些线程跟着退出,就可以把他设置为守护线程。

对这个基本的概念了解了之后我们再来看看线程的构造函数。


二、线程的构造函数


1、构造函数


线程Thread得构造函数一共有8个,

v2-d02006754c61077c2a03739299041b68_1440w.jpg

在这里我们接触到了一个新的类ThreadGroup。它代表的含义就是一个线程所属的线程组。在上面我们可以看到在实例化一个线程时候,既可以指定线程所属的线程组,也可以声明其runnable接口。下面我们分析一下这个线程组ThreadGroup。

他们俩的关系可以这样表示:

v2-c354e2c957544d13707b7a320c35e4de_1440w.jpg

在上面说我们能够指定线程所在的线程组,下面我们就代码演示一下。

public class Test {
    public static void main(String[] args) {
        //为当前线程设置线程组
        ThreadGroup group= new ThreadGroup("线程组");
        Thread thread = new Thread(group,"当前线程");
        thread.start();
        System.out.print(thread.getThreadGroup().getName());
    }
}
//输出:线程组

这就是其基本用法,但是如果我们没有指定线程所属的线程组输出会是什么结果呢?测试一下:

public class Test {
    public static void main(String[] args) {
        ThreadGroup group= new ThreadGroup("线程组");
        ThreadGroup maingroup = Thread.currentThread().getThreadGroup();
        Thread thread1 = new Thread(group,"线程A");
        Thread thread2 = new Thread("线程B");
        System.out.println(thread1.getThreadGroup().getName());
        System.out.println(thread2.getThreadGroup().getName());
        System.out.println(maingroup.getName());
    }
}
//输出:线程组 main main

上面的代码的意思是这样的,线程A指定了我们创建的线程组,线程B默认的线程组,maingroup是主线程组。根据输出结果我们会发现,如果一个线程没有指定线程组,那么他就和父亲的线程组是一样的。


2、实例化一个线程


上面给出了线程的八个构造方法,我们可以使用这八个构造方法去实例化一个线程,但是底层是如何做的呢?会不会是像普通类那样实例化的呢?对此我们就需要深入线程的源码去看看:

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

我们选用了一个最复杂的构造方法,因为其他构造方法都是其子集,我们可以看到,这里其实调用的是init方法,也就是说真正实现初始化的是在init方法中进行的。我们不妨跟进去看看:

private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
    init(g, target, name, stackSize, null, true);
}

这个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();
        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();
        //第四部分:runnable接口配置
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        //第五部分:设置栈大小
        this.stackSize = stackSize;
        //第六部分:设置线程ID
        tid = nextThreadID();
    }

终于找到了初始化线程的方法。我们划分了五个部分:


(1)第一部分:确保线程名字不能为空,

在这里就不得不提一句线程名了,java官方要求我们开发者如果没有显示的为线程指定一个名字,那么线程将以“Thread-”为前缀以数字为后缀,组成线程的名字。但是无论如何线程都需要有一个名字。

(2)第二部分:指定当前线程的线程组

里面的代码很明白,也就是说如果g不为空,我们就是用这个g作为当前线程的线程组,否则的话就使用父类的线程组。当然了,中间还要检查一下权限问题等等。

(3)第三部分:其他属性配置

在这里配置了是否设置为守护线程、优先级、类加载器等。

(4)第四部分:runnable接口配置

指定实现了runnable接口类。

(5)第五部分:设置栈大小

线程的栈大小 根据参数传递过程可以看出默认大小为零,即使用默认的线程栈大小

(6)第六部分:设置线程id

线程的ID是在nextThreadID方法中指定的。我们可以看看如何指定线程的ID的。

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

可以看到,其实就是threadSeqNumber。


OK,以上就是如何初始化一个线程,相信我们都比较清楚了,其他的构造函数只是对这个init方法的参数进行了一些改变而已。但是原理都是一样的。上篇文章中提到的一个问题还没有解决,接着往下看。


三、为什么调用start方法就能启动一个线程


为了解决这个问题我们还必须要深入源码看一下(jdk1.8):

public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }

这些代码的意思是什么呢?首先会判断线程状态是否异常,然后把当前线程在启动之前加入到线程组中,最后调用start0方法正式的启动线程。现在关键来了,真正启动线程的是这个start0方法,我们不妨再追进去看看:

v2-06fb0efeca58865656ae1d4dead9e884_1440w.jpg

也就是说真正启动时native方法启动的,好像也没有调用run方法,为什么run方法里面的内容就被执行了呢。官方文档是这么解释的:JNI方法start0内部调用了run方法。就是这么一句话就解释了上面的这个原因。


上面已经解决了两个问题,第一个就是构造函数,第二个也理解了为什么我们调用start方法就能启动一个线程而不是run。我们分析源码就能知道,线程提供的api方法基本上全部是native的

相关文章
|
6天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
31 7
|
12天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
12天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
40 12
|
12天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
6天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
6天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
23 3
|
6天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
12天前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
37 5
|
12天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
46 1
|
8天前
|
人工智能 移动开发 安全
家政上门系统用户端、阿姨端源码,java家政管理平台源码
家政上门系统基于互联网技术,整合大数据分析、AI算法和现代通信技术,提供便捷高效的家政服务。涵盖保洁、月嫂、烹饪等多元化服务,支持多终端访问,具备智能匹配、在线支付、订单管理等功能,确保服务透明、安全,适用于家庭生活的各种需求场景,推动家政市场规范化发展。