java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java线程属性

Java线程的属性深入理解

作者:摸鱼的老谭

本文介绍了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类型,不过只能获取并不能直接设置状态属性。

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

这几种状态之间可以互相转换,可以使用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;   // 最高优先级

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

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

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

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

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线程的属性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文