深入理解 Java Thread 构造函数

发布于 2024-05-24 19:11:46 字数 6680 浏览 23 评论 0

1、线程的默认命名

打开 JDK 的源码可以看到我们构造 Thread 的时候,默认的线程的名字是

  • Thread- 开头,从 0 开始计数;
  • Thread-0、Thread-1、Thread-2...
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

修改线程的名字,在线程启动之前,还有一个而已修改线程名字的机会,一旦线程启动,名字就不可以修改:

下面是在 Thread 中修改名字的代码:

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

    this.name = name;
    if (threadStatus != 0) {
        setNativeName(name);
    }
}

2、线程的父子关系

Thread 的所有构造函数,最终都会去调用一个静态方法 init ,我们截取片段代码对其进行分析,不难发现新创建的任何一个线程都会有一个父线程:

这里截取 Threadinit 的部分代码:

 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();
     this.group = g;// 设置线程组
     this.daemon = parent.isDaemon();// 当前线程是否为守护线程,取决于父线程
     this.priority = parent.getPriority(); // 设置优先级
     setPriority(priority);
     this.stackSize = stackSize;
     /* Set thread ID */
     tid = nextThreadID();
 }

上面代码中的 currentThread() 是获取当前线程,在线程生命周期中,我们说过线程的最初状态为 NEW,没有执行 start 方法之前,它只能算是一个 Thread 的实例,并不意味着一个新的线程被创建,因此 currentThread() 代表的将会是创建它的那个线程,因此我们可以得出以下结论。

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

我们都知道 main 函数所在的线程是由 JVM 创建的,也就是 main 线程,那就意味着我们前面创建的所有线程,其父线程都是 main 线程。

3、Thread 和 ThreadGroup

在 Thread 的构造函数中,可以显示的指定线程的 Group,也就是 ThreadGroup,下面看 init 方法的中间部分:

 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,那么子线程将会被加入父线程所在的线程组。

简单测试代码:

public class Code_04_ThreadGroupTest {

    public static void main(String[] args){
        PrintStream out = System.out;

        Thread t1 = new Thread("t1");// 没有给 t1 指定 group
        ThreadGroup group1 = new ThreadGroup("group1");
        Thread t2 = new Thread(group1, "t2");
        ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();

        out.println("Main Thread Group : " + mainGroup.getName());
        out.println(t1.getThreadGroup() == mainGroup); // true // 默认就是 main 线程的
        out.println(t2.getThreadGroup() == mainGroup); // false
        out.println(group1 == t2.getThreadGroup()); // true // 指定了就是这个了
    }
}

输出:

Main Thread Group : main
true
false
true

得出结论:

  • main 线程所在的 ThreadGroup 称为 main
  • 构造一个线程的时候如果没有显示的指定 ThreadGroup ,那么它将会和父线程属于同一个 ThreadGroup (且拥有同样的优先级);

4、Thread 和 JVM 虚拟机栈

1)、Thread 与 Stacksize

看下列 Thread 构造函数

Thread(ThreadGroup group, Runnable target, String name, long stackSize) 
//分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,作为 group 所引用的线程组的一员,并具有指定的堆栈大小。

构造 Thread 的时候传入 stackSize 代表着线程占用的 stack 大小,如果没有指定 stackSize 的大小,默认是 00 代表着会忽略该参数,改参数会被 JNI 函数( Native ) 去使用。

2)、JVM 内存结构

详见 JVM 相关知识。可看我的[另一篇博客](../../Java 基础/JVM/JVM 总结(一) - 内存区域与内存管理.md )。

3)、Thread 与虚拟机栈

虚拟机栈的大小大概是可以存放 21456 个栈桢(栈桢中存放局部变量表、操作数栈、动态链接...),而自己创建的线程的虚拟机栈只有大概 15534 个栈桢,但是我们可以在创建线程的时候指定 stackSize

5、守护线程

基本性质:

  • 在正常的情况下,如果 JVM 没有一个非守护线程,JVM 的进程才会退出。(当只有 Daemon 线程运行的时候才会退出);
  • setDaemon() 方法必须在 start() 方法之前调用(否则会抛出 IllegalThreadException );

线程是否为守护线程和它的父线程有很大的关系,如果父线程是正常线程,则子线程也是正常线程,反之亦然,如果你想要修改它的特性则可以借助 setDaemon 方法。 isDaemon() 方法可以判断该线程是不是守护线程。

另外需要注意的就是, setDaemon() 方法只在线程启动之前才能生效,如果一个线程已经死亡,那么再设置 setDaemon() 则会抛出 IllegalThreadStateException 异常。

看一个 daemonThread 的例子:

public class Code_05_DaemonThread {

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
           while(true){
               System.out.println("t running...");
               try {
                   Thread.sleep(1_000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
//        t.setDaemon(true);
        t.start();
        Thread.sleep(2_000);
        System.out.println("Main 线程结束!");
    }
}

上面注释了 t.setDaemon(true); 运行结果如下,发现 main 线程结束了,但是里面的线程没有结束。

但是如果不注释 t.setDaemon(true); ,当 main 线程结束,里面的线程就会结束。

守护线程的作用

如果一个 JVM 进程中没有一个非守护线程,那么 JVM 会退出,也就是说守护线程具备自动结束生命周期的特性,而非守护线程则不具备这个特点, 试想一下如果 JVM 进程的垃圾回收线程是非守护线程,如果 main 线程完成了工作,则 JVM 无法退出,因为垃圾回收线程还在正常的工作。再比如有一个简单的游戏程序,其中有一个线程正在与服务器不断地交互以获取玩家最新的金币、武器信息,若希望 在退出游戏客户端的时候,这些数据同步的工作也能够立即结束,等等。

守护线程经常用作与执行一些后台任务,因此有时它也被称为后台线程当你希望关闭某些线程的时候,或者退出 JVM 进程的时候,一些线程能够自动关闭,此时就可以考虑用守护线程( setDaemon() ) 为你完成这样的工作。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

述情

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

13886483628

文章 0 评论 0

流年已逝

文章 0 评论 0

℡寂寞咖啡

文章 0 评论 0

笑看君怀她人

文章 0 评论 0

wkeithbarry

文章 0 评论 0

素手挽清风

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文