深入理解Thread构造函数

Java 中提供了丰富的构造函数,在本文将介绍每一个构造函数,以及分析一些可能你未关注的细节
在这里插入图片描述

线程的命名

在构造线程的时候可以为线程起一个特殊意义的名字,有利于实际问题的排查和线程跟踪

线程的默认命名

    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

如果没有给线程显示命名,那么线程将以“Thread-”为前缀与一个自增的数字进行组合

命名线程
Thread提供了如下的关于命名的构造函数,通过构造一个友好的名称是一个很好的实战方式

Thread(String name)
Thread(Runnable target, String name)
Thread(ThreadGroup group, String name)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

修改线程名字
无论是使用默认的命名规则,还是指定一个特殊的名字,在线程启动之前都有一次机会进行修改,一旦线程启动之后,名字就不能修改了

    public final synchronized void setName(String name) {
        checkAccess();
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
        if (threadStatus != 0) {                // 当线程状态不是New的时候,不允许修改线程名称
            setNativeName(name);
        }
    }
线程的父子关系

通过源码,我们发现Thread 的所有构造函数,最终都会去调用一个静态方法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();
        .......
        }

其中currentThrea() 是获取当前线程,我们知道在线程生命周期中,线程最初的状态为New,没有执行start方法,它只能算一个Thread实例,这里currentThrea() 代表的是创建它的那个线程,因此我们可以得出

  • 一个线程的创建肯定是由另外一个线程完成的
  • 被创建的父线程是创建它的线程

我们知道main函数所在的线程是JVM创建,也就是main线程,意味我们之前创建所有线程的父线程都是main线程

Thread 与 ThreadGroup

通过继续阅读源码发现在Thread的创建中,我们可以显示的指定线程的Group

        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

当构造Thread没有显示指定ThreadGroup时候,子线程将会加入父线程所在的线程组

  • main线程所在的ThreadGroup 称为main
  • 构造一个线程的时候如果没有显示指定ThreadGroup,那它将会和父线程同属于一个ThreadGroup

默认设置中,子线程会和父线程除了同属于一个Group之外,它还会和父线程拥有同样的优先级,同样的daemon

Thread 与 Runnable

Thread负责线程相关的职责和控制,而Runnable负责逻辑执行单元部分

Thread 与 虚拟机栈

在Thread的构造函数中,我们发现一个特殊的参数stackSize,这个参数的作用是什么?
一般情况下,创建线程的时候不需要手动指定栈内存的地址空间字节数组,但通过stacksize的设置可以发现,stackSize越大,线程内方法调用递归的深度就越深,stacksize越小创建的线程数量越多,当然这个参数高度依赖平台

JVM内存结构
JVM在执行Java程序的时候会把对应的物理内存划分不同的区域,每个区域都存放不同的数据,也有不同的创建和销毁时机,有些分区在JVM启动的时候就创建,有些则在运行才创建,java1.8之前,JVM内存结构大致如下图所示:
在这里插入图片描述
程序计数器
程序计数器在JVM中作用就是存放当前线程接下来要执行的字节码指令,分支,循环,跳转,异常处理信息,为了在CPU时间片轮换上下文之后顺利回到正确的执行位置,每条线程都需要一个独立的程序计数器,各个线程互不影响,因此JVM将此块区域设计成线程私有

java虚拟机栈
虚拟机栈是与线程紧密关联的一块区域,与程序计数器相似,Java虚拟机栈也是线程私有的,它的生命周期与线程相同,是在JVM运行时所创建的,在线程中,方法在执行的时候会创建一个名为栈帧的数据结构,主要存放局部变量表,操作栈,动态链接,方法出口等信息,如下图,方法的调用对应着栈帧在虚拟机中压栈和出栈的过程
在这里插入图片描述
每一个线程在创建的时候,JVM都会为其创建对应的虚拟机栈,虚拟机栈的大小可以通过-xss来配置,方法的调用是栈帧被压入和弹出的操作,同等的虚拟机栈如果局部变量表占用内存越少,则被压入栈帧数量就会越多

本地方法栈
Java中提供调用本地方法的接口,也就是C/C++程序,在线程执行过程中,经常会碰到调用JNI方法的情况,比如网络通信,文件操作等底层,JVM为本地方法划分的内存区域就是本地方法栈,这块内存区域自由度很高,完全依靠JVM厂商实现,同样也是线程私有的内存区域

堆内存
堆内存是JVM中最大一块内存区域,被所有的线程共享,Java运行期间创建的所有对象几乎存在此块区域里,该内存区域也是回收器重点照顾的区域,因此也称为GC堆,堆内存一般会细分为新生代和老年代,新生代更细致划分为Eden区,From Survivor区和To Survivor区

方法区
方法区也是被多个线程共享的区域,主要存储以及被虚拟机加载的类信息,常量,静态变量,即时编译器(JIT) 编译后的代码等数据,虽然在Java虚拟机规范中,将方法区划分为堆内存中的一个逻辑分区,但它还是经常被称为“非堆”,有时候也称为“持久代”

Java8 元空间
自java1.8之后,JVM内存发生了一些改变,实际上持久代内存被彻底删除,这部分区域被Meta space代替,不同的是元空间使用的本地内存

为什么移除持久代

  • 它的大小是在启动时固定好的——很难进行调优。-XX:MaxPermSize,设置成多少好呢?
  • HotSpot的内部类型也是Java对象:它可能会在Full GC中被移动,同时它对应用不透明,且是非强类型的,难以跟踪调试,还需要存储元数据的元数据信息(meta-metadata)
  • 可以在GC不进行暂停的情况下并发地释放类数据
  • 使得原来受限于持久代的一些改进未来有可能实现
守护线程

守护线程是一类比较特殊的线程,一般用来处理一些后台的工作,比如JDK垃圾回收线程
在正常情况下,若JVM中没有一个非守护线程,则JVM进行会退出,需要注意setDaemon方法必须在线程启动之前设置才有效

守护线程具有自动结束生命周期的特性,而非守护线程不具备,比如在一个简单游戏中,其中一个线程能不断与服务器交互获取玩家最新金币和武器信息,若希望在退出客户端时候,这个线程也能立即结束,我们往往可以将这个线程设置为守护线程

守护线程一般执行一些后台任务,如果你希望关闭某些线程的时候,或者退出JVM进程时候,这些线程能自动关闭,可以考虑用守护线程来完成这样的工作

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值