Java线程的属性深入理解

 更新时间:2025年11月07日 10:57:27   作者:摸鱼的老谭  
本文介绍了Java线程的几个核心属性,包括线程ID、线程名称、线程状态、优先级、守护线程、是否存活以及异常处理器,通过这些属性的操作,可以更好地理解和控制线程的行为,感兴趣的朋友跟随小编一起看看吧

上一节创建了线程后,本小节着重关注线程的属性,在Thread类中提供各种方法用于获取或设置相关属性,我们通过这些属性的操作加深对线程的理解。

20.1 线程ID

线程类中有一个属性 tid,它是一个正的长整型数字,在创建此线程时生成,用于标识此线程实例。线程实例的该属性是唯一的,并在其生命周期内保持不变。

使用方法 threadId( ) 获取线程属性tid,使用方式如下:

Thread thread = new Thread();
System.out.println(thread.threadId());    // 输出 36

另外还有一个在Java 19标识为过时的方法getId()与threadId()效果一样,实际上内部就是直接调用threadId()方法。

20.2 线程的名称

Thread类中的属性name用于表示线程的名称,默认产生规则如下:

this.name = (name != null) ? name : genThreadName();

genThreadName方法体如下:

return "Thread-" + ThreadNumbering.next();

后面的静态方法next()产生从0递增的整数,内部使用线程安全的方式实现。

例如下面的例子:

Thread thread0 = new Thread();
System.out.println(thread0.getName());
Thread thread1 = new Thread();
System.out.println(thread1.getName());
Thread thread2 = new Thread(() -> {
    Thread thread3 = new Thread();
    System.out.println(thread3.getName());
});
System.out.println(thread2.getName());
thread2.start();

运行之后,输出的线程名是:

Thread-0
Thread-1
Thread-2
Thread-3

如果我们显式设置线程的name属性,则可以调用setName为线程赋一个自定义名称:

Thread thread0 = new Thread();
thread0.setName("这是Thread0");
System.out.println(thread0.getName());  
Thread thread1 = new Thread();
System.out.println(thread1.getName());

运行之后,输出的线程名如下,可以看出来,其他的线程默认名称没有影响:

这是Thread0
Thread-1

20.3 线程的状态

线程从创建到结束运行,中间会有若干状态,在内部使用FieldHolder类型属性holder封装了一个 threadStatus 属性表示线程的状态,它是一个int类型,不过只能获取并不能直接设置状态属性。

线程的状态有以下几种,但同一时刻只有一种状态:

  • 新创建(New):使用new创建一个线程时,代表线程的对象已经被初始化,但尚未调用start方法
  • 可运行(Runnable):一旦调用了start方法,就是可运行的,但这只说明线程目前处于的状态
  • 被阻塞(Blocked):线程是可以执行的,但由于某些因素的阻碍处于停滞状态,一般是正在等待一个锁,以访问某个对象
  • 等待(Waiting):无限期的等待另一个线程来执行某一个动作
  • 计时等待(Timed waiting):在指定限期内等待另一个线程来执行某一个动作
  • 被终止(Terminatewd):线程的正式结束方式,run方法执行完毕并返回

这几种状态之间可以互相转换,可以使用Thread的getState()方法获取其当前状态:

State getState()

返回的结果是State枚举类型,其实例如下:

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

需要注意的是在调用 getState() 方法之后,线程的状态可能会发生变化。因此,通过调用 getState() 所获得的状态可能无法反映线程在此刻的实际状态。例如下面的代码中,状态会随着代码的执行有变化:

Thread thread = new Thread(() -> {
    System.out.println(Thread.currentThread().getState());   // RUNNABLE
});
System.out.println(thread.getState());                       // NEW
thread.start();
System.out.println(thread.getState());                       // RUNNABLE
Thread.sleep(1000);
System.out.println(thread.getState());                       // TERMINATED

20.4 线程的优先级

Thread 类中的优先级也是被内部属性 holder 封装的一个 int 属性 priority,不过Thread类提供的对于优先级的get与set方法。

线程优先级由线程调度器用于决定每个线程何时能够运行。理论上,在给定的一段时间内,优先级较高的线程会比优先级较低的线程获得更多的 CPU 时间。但在实际操作中,一个线程所获得的 CPU 时间通常取决于除其优先级之外的多个因素。(例如,操作系统实现多任务处理的方式会影响 CPU 时间的相对可用性。)优先级较高的线程也可以抢占优先级较低的线程。例如,当一个优先级较低的线程正在运行,而一个优先级较高的线程恢复运行(例如从睡眠或等待 I/O 事件中恢复)时,它将抢占优先级较低的线程。

理论上,具有相同优先级的线程应当能够平等地使用 CPU 资源。不过要注意,Java 是为多种环境设计的,其中一些环境对多任务处理的实现方式与其他环境截然不同。为了安全起见,具有相同优先级的线程应当偶尔让出控制权。这能确保在非抢占式操作系统中,所有线程都有机会运行。实际上,即使在非抢占式环境中,大多数线程仍有机会运行,因为大多数线程不可避免地会遇到一些阻塞情况,比如等待输入/输出操作。当这种情况发生时,被阻塞的线程会被暂停,其他线程可以运行。但是,如果我们希望实现流畅的多线程执行,最好不要依赖这一点。此外,某些类型的任务是 CPU 密集型的。这类线程会占据 CPU 的大部分时间。对于这些类型的线程,应该偶尔让出控制权,以便其他线程能够运行。

线程优先级用数字来表示,范围从1到10;主线程的缺省优先级是5,子线程的优先级默认与其父线程相同。

在Thread类中声明了三个与优先级相关的常量:

public static final int MIN_PRIORITY = 1;    // 最低优先级
public static final int NORM_PRIORITY = 5;   // 线程的默认优先级
public static final int MAX_PRIORITY = 10;   // 最高优先级

获取与设置优先级的方法:

  • final int getPriority():获取线程的优先级
  • final void setPriority(int newPriority):设置线程的优先级,参数newPriority应在1-10之间

下面的示例演示了优先级设置与获取方法的使用:

Thread thread = new Thread(() -> {
});
System.out.println(thread.getPriority());     // 输出 5 
thread.setPriority(7);                        // 设置优先级为 7
System.out.println(thread.getPriority());     // 输出 7

20.5 守护线程

Thread类的属性holder中封装了属性daemon表示该线程是否是守护线程。

守护(Daemon)线程是一种服务提供者线程,而非守护线程(或用户线程)则是使用守护线程所提供服务的线程。服务提供者在没有服务消费者时不应存在。JVM 会遵循这一逻辑,当 JVM 发现应用程序中的所有线程均为守护线程时,它会退出该应用程序。请注意,如果应用程序中只有守护线程,JVM 在退出应用程序前不会等待这些守护线程完成。

可以通过使用 setDaemon() 方法并传入 true 参数来将一个线程设为守护线程。在启动线程之前必须先调用线程的setDaemon() 方法。否则,会抛出 IllegalThreadStateException 异常。可以使用 isDaemon() 方法来检查一个线程是否为守护线程。

当创建一个线程时,该线程的 daemon 属性与创建该线程的线程的 daemon 属性相同。换句话说,新线程会继承其创建线程的 daemon 属性。

Thread thread = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        System.out.println("你好");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
});
thread.setDaemon(true);
thread.start();
System.out.println("主线程");

运行之后结果是:

主线程
你好

当主线程结束后,因为只有守护线程,应用程序会退出。

但是如果不是守护线程,如下:

Thread thread = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        System.out.println("你好");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
});
thread.start();
System.out.println("主线程");

运行结果如下:

主线程
你好
你好
你好
你好
你好

可以看到,线程执行完成后应用程序会退出。

判断一个线程是否守护线程,可以使用以下方法:

public final boolean isDaemon(){}

该方法用于测试线程是否为守护线程。要注意的是虚拟线程的守护状态始终为 true。

20.6 是否存活

内部属性eetop仅供 JVM 专用,保存底层 VM JavaThread 的地址,在线程启动时设置为非零值,在线程终止时重置为零。非零值表示此线程处于存活状态,使用方法 isAlive( ) 判断线程是否处于存活状态。

Thread thread = new Thread(() -> {
});
thread.start();
System.out.println(thread.isAlive());    // true
Thread.sleep(500);                       // 休眠500毫秒,异常已处理
System.out.println(thread.isAlive());    // false

20.7 设置异常处理器

当线程中向外抛出了未捕获异常时,可以为线程实例设置一个异常处理对象到uncaughtExceptionHandler属性,该实例的类型是 Thread.UncaughtExceptionHandler 的实现,如:

public class MyThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println(t.getName()+":"+e.getMessage());
    }
}

上面重写的方法 uncaughtException() 接收两个参数,第一个参数t是抛出异常的线程,第二个参数是抛出异常对象。

下面的代码中,创建了新的线程,在其中抛出了异常,不过随后使用 setUncaughtExceptionHandler 方法设置了异常处理类的实例,则线程任务运行时的未捕获异常会被 uncaughtException() 方法捕获到:

Thread thread = new Thread(() -> {
    throw new RuntimeException(Thread.currentThread().getName() + "中的异常");
});
MyThreadExceptionHandler exceptionHandler = new MyThreadExceptionHandler();
thread.setUncaughtExceptionHandler(exceptionHandler);
thread.start();

运行之后的结果为:

Thread-0:Thread-0中的异常

除了上面的 setUncaughtExceptionHandler 方法之外,Thread类还提供了一个静态方法用于为所有线程设置异常处理逻辑,不过它的处理逻辑会被非静态的 setUncaughtExceptionHandler 覆盖:

MyThreadExceptionHandler exceptionHandler = new MyThreadExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);

对于异常处理的流程如下:

  • 如果线程使用 setUncaughtExceptionHandler() 方法设置了异常处理器,则会调用该处理器的 uncaughtException() 方法。
  • 如果一个线程没有设置异常处理器,那么将调用其线程组的 uncaughtException() 方法,如果该线程组有父线程组,则调用其父线程组的 uncaughtException() 方法。否则,检查是否设置了默认的异常处理器。如果找到了默认的未异常处理器,则调用其 uncaughtException() 方法。如果没有找到默认的异常处理器,则在标准错误流上打印一条消息。如果既未找到默认的异常处理器,又抛出了 ThreadDeath 异常,则不执行任何操作。

20.8 小结

本节详细介绍了线程的几个核心属性,主要有:1)线程ID(tid)是唯一标识符,通过threadId()获取;2)线程名称(name)默认为"Thread-数字",可通过setName()修改;3)线程状态(threadStatus)包括NEW、RUNNABLE等6种,使用getState()获取;4)优先级(priority)范围1-10,通过get/setPriority()方法操作;5)守护线程(daemon)属性决定线程是否随主线程结束,通过is/setDaemon()方法管理。

到此这篇关于Java学习之旅第三季-20:线程的属性的文章就介绍到这了,更多相关Java线程的属性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Spring Boot 目录文件结构

    详解Spring Boot 目录文件结构

    这篇文章主要介绍了Spring Boot 目录文件结构的相关资料,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • MyBatis中如何接收String类型的参数实现

    MyBatis中如何接收String类型的参数实现

    这篇文章主要介绍了MyBatis中如何接收String类型的参数实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • 通过AOP环绕通知如何实现事务控制

    通过AOP环绕通知如何实现事务控制

    这篇文章主要介绍了通过AOP环绕通知如何实现事务控制的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • Java file类中renameTo方法操作实例

    Java file类中renameTo方法操作实例

    renameTo()方法是File类的一部分,renameTo()函数用于将文件的抽象路径名重命名为给定的路径名​​,下面这篇文章主要给大家介绍了关于Java file类中renameTo方法操作的相关资料,需要的朋友可以参考下
    2022-11-11
  • java身份证合法性校验工具类实例代码

    java身份证合法性校验工具类实例代码

    这篇文章主要给大家介绍了关于java身份证合法性校验工具类的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Springboot jdbctemplate整合实现步骤解析

    Springboot jdbctemplate整合实现步骤解析

    这篇文章主要介绍了Springboot jdbctemplate整合实现步骤解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • Java HttpClient-Restful工具各种请求高度封装提炼及总结

    Java HttpClient-Restful工具各种请求高度封装提炼及总结

    这篇文章主要介绍了Java HttpClient-Restful工具各种请求高度封装提炼及总结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • 详解Spring Cloud Zuul中路由配置细节

    详解Spring Cloud Zuul中路由配置细节

    本篇文章主要介绍了详解Spring Cloud Zuul中路由配置细节,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • java学习之jar包的下载和导入

    java学习之jar包的下载和导入

    我们经常碰到有些jar包在中央仓库没有的情况,这时候我们需要导入,这篇文章主要给大家介绍了关于java学习之jar包的下载和导入的相关资料,需要的朋友可以参考下
    2023-06-06
  • idea开启热部署Devtools的步骤详解

    idea开启热部署Devtools的步骤详解

    当我们在 idea 中修改代码的时候,idea 并不会自动的重启去响应我们修改的内容,而是需要我们手动的重新启动项目才可以生效,这个是非常不方便,但是可以在 idea 中开启这个自动热部署的功能,本文给大家介绍了idea开启热部署Devtools的步骤,需要的朋友可以参考下
    2024-03-03

最新评论