玩转并发-深入理解Thread构造函数

线程的默认命名

下面的几个构造函数,并没有为线程提供命名的参数,那么此时线程会有怎样的命名呢?

Thread()
Thread(Runnable target)
Thread(ThreadGroup group, Runnable target)

打开JDK的源码:

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

如果没有为线程显氏地命名,那么线程会以Thread为前缀与一个自增的数字组合。

线程的父子关系

Thread的所有构造函数都会去调用一个静态方法init。通过查看JDK下的源码,我们发现新创建的任何一个线程都会有一个父线程。

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

上面代码中的currentThread()是获取当前线程。在执行到此处是,还没有调用start()方法,此时只是一个Thread实例,并不意味着新线程的创建,因此currentThread()代表的是创建它的线程。

  1. 一个线程的创建肯定是由另一个线程进行的
  2. 被创建线程的父线程是创建它的线程

Thread与ThreadGroup

JDK源码中关于ThreadGroup:

if (g == null) {
            /* 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,那么子线程会加入父线程的ThreadGroup。main所在的线程组叫main

Thread与Runnable

我们查看Thread构造函数的API,发现并不是所有的构造函数都会注入Runnable。
查看JDK源码:
先回顾下上篇文章讲过的start()的源码

public synchronized void start() {
      
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
        	//start0()是native方法,内部调用了run()
        	//run的来源可以是Thread的复写,或者是Runnable策略接口的注入
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }  
  //Thread的成员变量
  private Runnable target;

  //init中的语句
  //将参数列表中的target传入给成员变量target
  this.target = target;
   
    public void run() {
        if (target != null) {
        	//调用Thread声明的run()
            target.run();
        }
    }

如果在构造Thread中没有传递Runnable或者没有复写run(),该Thread将不会调用任何东西;如果传递了Runnable接口,或者复写了Thread的run()方法,则会执行该方法的逻辑单元。


Thread与JAVA内存结构的关系

JAVA内存结构

JVM内存结构图:
在这里插入图片描述

方法区

方法区是多个线程共享的内存区域,主要用于存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器(JIT)编译后的代码等数据。又被称为“非堆”。

堆内存

堆内存是JVM中最大的一块区域,被所有的线程共享。Java在运行期间创建的所有对象几乎都存放在该区域.该内存区域是垃圾回收器重点照顾的对象,因此又被叫作GC堆。

本地方法栈

Java中提供了调用本地方法的接口(Java Native Interface),也就是C/C++程序。JVM为本地方法所划分的内存区域便是本地方法栈。

程序计数器

每个线程都需要有一个独立的程序计数器,方便CPU时间片轮转切换上下文之后能够回到正确原来的位置。JVM将此块内存区域设为线程私有。

虚拟机栈

虚拟机栈也是线程私有的,生命周期与线程相同。在线程中,方法在执行时都会创建一个名叫栈帧的数据结构,主要用于存放变量表,操作栈,动态链接,方法出口等信息。
在这里插入图片描述将栈帧的大小称为宽度,栈帧的数量称为虚拟机栈的深度。在构造Thread时,可以指定虚拟机栈的深度。


守护线程

守护线程是一些比较特殊的线程,一般用于处理后台的工作,比如JVM的垃圾回收。在正常情况下,若JVM中没有一个非守护线程,则JVM进程会退出

守护线程作用

如果JVM进程中没有一个非守护线程,那么JVM会退出,也就是说守护线程具备自动结束生命周期的特性。试想下如果JVM的垃圾回收线程不是守护线程,那么当main工作完成了,JVM仍无法退出,因为垃圾回收线程仍在工作。当希望某个线程执行结束,另一线程也结束,可以将后者设置为前者的守护线程(setDaemon)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值