博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java多线程(3)线程构造函数(源码剖析)
阅读量:4035 次
发布时间:2019-05-24

本文共 6092 字,大约阅读时间需要 20 分钟。

在上一篇文章中对线程状态生命周期和常见的线程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个,

在这里插入图片描述

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

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

在这里插入图片描述

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

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方法,我们不妨再追进去看看:

在这里插入图片描述

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

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

中…(img-Fcuds1ZI-1566291437770)]

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

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

在这里插入图片描述

转载地址:http://hmbdi.baihongyu.com/

你可能感兴趣的文章
[互联网关注]李开复教大学生回答如何学好编程
查看>>
[关注大学生]李开复给中国计算机系大学生的7点建议
查看>>
[关注大学生]大学毕业生择业:是当"鸡头"还是"凤尾"?
查看>>
[茶余饭后]10大毕业生必听得歌曲
查看>>
gdb调试命令的三种调试方式和简单命令介绍
查看>>
C++程序员的几种境界
查看>>
VC++ MFC SQL ADO数据库访问技术使用的基本步骤及方法
查看>>
VUE-Vue.js之$refs,父组件访问、修改子组件中 的数据
查看>>
Vue-子组件改变父级组件的信息
查看>>
Python自动化之pytest常用插件
查看>>
Python自动化之pytest框架使用详解
查看>>
【正则表达式】以个人的理解帮助大家认识正则表达式
查看>>
性能调优之iostat命令详解
查看>>
性能调优之iftop命令详解
查看>>
非关系型数据库(nosql)介绍
查看>>
移动端自动化测试-Windows-Android-Appium环境搭建
查看>>
Xpath使用方法
查看>>
移动端自动化测试-Mac-IOS-Appium环境搭建
查看>>
Selenium之前世今生
查看>>
Selenium-WebDriverApi接口详解
查看>>