[多线程] - 从Thread类的构造函数分析创建一个线程需要的基本元素

一、Thread类的构造函数

在实际的开发中,如果我们想要启动一个新的线程任务需要怎么做?没错,就是调用Thread类的start()方法启动一个新的线程任务。那么一个Thread对象都需要哪些必要的条件作为新线程启动的基本元素呢?我们就可以在Thread类的构造函数下手分析。

Thread t1 =new Thread();

那么一个空参的构造函数都为我们做了些什么呢?我们点进构造函数可以看到如下代码:

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

Thread类在一个空参的构造函数中调用了一个叫做init的方法,我们继续进入init方法往下看:

	private void init(ThreadGroup paramThreadGroup, Runnable paramRunnable, String paramString, long paramLong) {
    
    
		init(paramThreadGroup, paramRunnable, paramString, paramLong, null, true);
	}

在init方法中,它调用了自身的重载方法init,我们今天就需要从这个重载方法的参数中分析一个创建一个线程最基本的元素。当然,我们还是着重分析在Thread类提供的构造器中提到的参数,这样更方便我们理解Thread类在生成的时候,哪些参数是最重要的。首先我们打开JDK的文档,找到Thread类,然后查看JDK为我们提供了哪些构造方法:
在这里插入图片描述

1.线程任务(Runnable)

首先这里是我纠结了很久的地方。因为在JDK文档中说到,创建线程的两种方式是继承thread类或者实现runnable接口,而在我们真正的想要实现多线程业务的时候,是必须通过thread类的start()方法才能够实现,而直接调用实现runnable对象的run方法依托的还是当前线程处理业务。就比如如下代码:

package xiao.thread.constructor;

public class Demo01 implements Runnable{
    
    
	@Override
	public void run() {
    
    
		System.out.println("当前线程名是:"+Thread.currentThread().getName());
	}
	public static void main(String[] args) {
    
    
		new Demo01().run();
	}
}

我们通过new创建了一个实现了runnable接口的实例,并且在run方法中打印出他此时正在使用的线程名称,结果发现:
在这里插入图片描述
该实例并没有实现新的线程,而是使用main线程继续处理run方法体内的逻辑,而如果我们想要通过新的线程处理run方法体内的逻辑,就需要这么写:

package xiao.thread.constructor;

public class Demo01 implements Runnable{
    
    
	@Override
	public void run() {
    
    
		System.out.println("当前线程名是:"+Thread.currentThread().getName());
	}

	public static void main(String[] args) {
    
    
		new Thread(new Demo01()).start();
	}
}

此时的结果是:
在这里插入图片描述
此时业务单元才是真正的启动了一个新的线程处理我们想要的任务逻辑。那么JDK的API文档中说的两种创建方式是不是有问题呢?其实我觉得这个应该是对于这句话的理解出了问题。通过上述的代码我们不难看出,实现runnable接口并没有为我们启动一个线程,而是提供了一个可以被线程执行的任务。那么我们就可以理解为实现runnable接口和继承Thread类其实都是为我们创建了一个线程任务,这个任务本身是无法被启动的,当我们想要启动这个任务的时候,只能够依托于已有的线程才可以。而想要通过新的线程实现这个任务,就需要通过Thread类中的本地方法start来实现。所以Thread类在构造函数中为我们提供了一个参数
在这里插入图片描述
使用这个有参构造就可以新启动一个线程来实现我们想要完成的线程任务。当然我们也可以直接使用匿名内部类的方式来创建一个线程任务:

	public static void main(String[] args) {
    
    
		new Thread(() -> {
    
    
			System.out.println("当前线程名是:" + Thread.currentThread().getName());
		}).start();;
	}

2. 线程名

在阿里巴巴19年出的编程规范中,明确规定了我们需要为线程起个有意义的名字:
在这里插入图片描述
原因也说得很清楚了,是为了再出现问题的时候回溯。当然这个只是阿里巴巴倡导的一个规范,当我们在新建线城时,如果不指定线程名称将怎么样呢?在Thread类的init方法中,我们可以看到下行代码:

		init(null, null, "Thread-" + nextThreadNum(), 0L);

当我们没有为新的线程指定名字的时候,线程将会以Thread-开头,然后拼接上一个nextThreadNum得到的数来记录当前线程的名称。这个名称由于相似度太高,当线上出现问题的时候无疑会使我们的工作量大量增加。就比如:
在这里插入图片描述
我们使用jstack命令追查线程的时候,由于这些没有参考意义的线程名称,让我们无法第一时间定位是哪个模块出现了问题。所以我们在创建线程的时候,最好还是将当前业务的信息放到线程名称中,方便回溯。

3. 线程组(ThreadGroup)

首先我们要先看下线程组的定义:

线程组(ThreadGroup)就是由线程组成的管理线程的类
在Java中每一个线程都归属于某个线程组管理的一员,例如在主函数main()主工作流程中产生一个线程,则产生的线程属于main这个线程组管理的一员。

线程组很好理解,就是用来管理线程的。而线程组之间得关系,很像是中国的家族式管理,每个人都有一个脉细,绝不可能是游离的,而线程也都可以找到他隶属的线程组。而线程组在日常工作中应用并不多,主要用对一批线程进行管理,如中断(interrupt)/暂停(suspend)/恢复(resume)/终止(stop)等操作,这里注意下ThreadGroup的destroy方法,是清除掉这个线程组及其所有子线程组,但要注意这里的所有线程组包括子线程组必须是空的,也就是说线程组里的线程都销亡了,否则会抛出异常。
附:线程组的管理方式

1)自动归属,即线程或线程组具有默认机制,通俗地说,创建一个线程或线程组,它必有自己的所属的线程组,每个线程都不可能是游离的,这可以通过查看new Thread()和new ThreadGroup()的源码查阅便知。
2) 父对象可以创建子对象,子对象依旧可以创建子对象,因为线程组采用的是树结构实现。

4. 栈大小 (stackSize)

在这里插入图片描述
作为Thread的最后一个构造函数,stackSize主要是用来指定新建线程的栈空间大小,这个大小在源码中也规定了默认值:
在这里插入图片描述
那么为什么要是0呢?
JVM文档中明确的解释了这个问题,是由于stackSize这个参数是否生效,是取决于jvm运行的平台,当该平台支持stackSize参数指定栈空间大小的时候,jvm会取用该平台支持的最小值来初始化栈空间大小。所以参数为0代表着创建该平台支持的最小栈内存大小。

在这里插入图片描述

二、总结

通过对Thread类的构造函数分析,我们主要知道了创建线程时主要需要以下四个元素:

  1. runnable提供的线程任务:这里我们需要注意,在JDK的API文档中曾提到的
    在这里插入图片描述
    当我们想要的仅仅是希望实现业务分离或拓展的时候,最好不要通过集成Thread的方式来实现,除非你希望增强或集成Thread的功能,否则应该选用实现runnable接口就可以了。
  2. 线程名称 :为线程制定一个符合业务的名称可以为我们在后期排查问题的时候节省很多的时间
  3. 线程组: 指定特定的线程组方便我们快捷的对特定业务线程进行维护
  4. 线程大小 :创建指定大小的线程也可以方便我们计算栈内存空间的利用情况

猜你喜欢

转载自blog.csdn.net/xiaoai1994/article/details/109801909