一文掌握 Thread 类里的常用方法(yield、interrupted、join、daemon)

Thread 类是 Java 中表示线程的类,而 Java 编程最大的优点就是在于其对多线程的支持,我们学 Java 肯定要学好这个 Java 的精髓。

之前,我用一篇文章介绍了 Object 类中的所有方法,大家可以阅读一下,其中有两个方法还跟本文中的线程的方法有关:一文掌握 Object 类里的所有方法(wait、notify、finalize)

今天,我们同样,用一篇文章讲解 Thread 类里的常用方法。之所以不讲所有方法,是因为相比于 Object 类,Thread 类中的方法太多了,不能一一介绍,只能介绍一些常用的方法。

获取当前线程对象 Thread.currentThread()

public static native Thread currentThread();

返回对当前正在执行的线程对象的引用。

线程休眠 Thread.sleep(long millis)

public static void sleep(long millis) throws InterruptedException
public static void sleep(long millis, int nanos) throws InterruptedException

使当前线程休眠指定的时间,当前线程将进入 TIMED_WAITING 状态,此状态表示线程在等待一段指定的时间后再继续执行。

这样线程暂停执行,就会让出 CPU 资源,让其他线程有机会获得 CPU 时间片。

这个方法估计也是线程中用来模拟延迟最常用的方法了。

让出 CPU 使用权 Thread.yield()

public static native void yield();

yield 这个单词在此处翻译为中文一般是 让步让出 的意思。而此方法的作用就是让当前线程主动让出 CPU 的使用权,使其他同优先级的线程有机会获得执行时间。这并不保证当前线程会立即停止执行或其他线程会立即开始执行,它只是一个提示,具体行为依赖于操作系统的线程调度机制。

因此在这个地方,先插入一个 Java 线程切换状态图:

当调用 Thread.yield() 时,线程调度器会根据一下规则做出决策:

  1. 当前线程放弃 CPU:当前线程从运行状态转变为可运行状态(READY state),并重新进入线程调度器的队列。
  2. 线程调度器重新选择线程:调度器将从所有可运行状态的线程中选择下一个要执行的线程。这可能是当前线程,也可能是另一个具有相同或更高优先级的线程。

由于这个方法由于只是一个提示,具体行为是不确定的,所以这里也没法代码演示。实际上我试过几段代码,看起来没有效果的样子。

线程中断 interrupted()isInterrupted()Thread.interrupted()

public void interrupt()
@FastNative public native boolean isInterrupted();
@FastNative public static native boolean interrupted();

interrupt 这个词很容易让人产生误解。从字面意思来看,好像是所一个线程运行到一半,将其中断。而实际上,这里的中断可以简单理解为是一个线程的 boolean 的标志位。

其他线程可以调用线程的 interrupted 方法向这个线程发送中断信号,而这个收到中断信号的线程,会根据当前所处的状态做出不同的反应。我们看下面的代码:

public static void main(String[] args]) {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            while (true) {
                if(Thread.interrupted()) {  //检查并清除中断标记
                    System.out.println("子线程已被中断...");
                    break;
                }
                //do some work...
                System.out.println("子线程正在运行...");
            }
        }
    });

    thread.start();
    
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    
    System.out.println("未调用子线程 interrupt,当前子线程中断状态 " + thread.isInterrupted());
    thread.interrupt();
    System.out.println("已调用子线程 interrupt,当前子线程中断状态 " + thread.isInterrupted());
}

我们先让子线程跑起来,1秒后调用这个线程的 thread.interrupt() 方法,那么在子线程中,就可以通过 Thread.interrupted() 检查中断标记并做出反应。也可以通过 thread.isInterrupted() 来获取线程的中断状态。
我们看一下结果:

System.out                  I  ......
System.out                  I  子线程正在运行...
System.out                  I  子线程正在运行...
System.out                  I  主线程未调用子线程 interrupt ... 当前子线程中断状态 false
System.out                  I  主线程已调用子线程 interrupt ... 当前子线程中断状态 true
System.out                  I  子线程已被中断... 子线程中断状态 false

主线程使用 thread.interrupt() 向子线程发送中断信号后,子线程通过 Thread.interrupted() 来检查中断标记来做出反应,而且在这个方法调用后,子线程的中断状态也会被重置。

前面还说,子线程收到中断信号时,会根据当前所处的状态做出不同的反应。那从这里例子中也没看到什么反应啊?因为当前的例子是一个正常的反应,准确的说,就是子线程该干嘛干嘛。但是如果此时子线程处于 WAITING 或者 TIMED_WAITING 状态,那么就会抛出一个 InterruptedException 异常。

线程如果调用了阻塞的函数,那么就会进入刚说的 WAITING 或 TIMED_WAITING 状态,两者的区别只是前者为无限期阻塞,后者则传入了一个时间参数,阻塞有一个时间的限制。实际上,只有那些声明了会抛出 InterruptedException 的函数才会抛出异常,也就是下面的这些常用函数:

public static void Thread.sleep(long millis) throws InterruptedException
public final void Object.wait() throws InterruptedException
public final void Thread.join() throws InterruptedException

守护线程 setDaemon(boolean)isDaemon

public final void setDaemon(boolean on) 
public final boolean isDaemon()

在说守护线程这个概念之前,我们先看一段代码:

public class JavaMain {

    public static void main(String[] args) {
        System.out.println("main thread enter");

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    try {
                        System.out.println("thread 线程正在运行...");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();

        System.out.println("main thread exit!");
    }
}

这段代码在 main 方法中开启了一个线程,这个线程是个死循环,不断地循环打印。于是问题就来了:在 main 函数退出之后,这个线程还会执行吗?还是整个进程都会强制退出?

大家可以先想一下,或者把代码运行起来试一试。

答案是不会的。因为代码中我们创建的线程 thread,是一个普通线程,也就是非守护线程。

当在一个 JVM 进程里面创建线程时,这些线程被分为两类:守护线程和非守护线程。默认创建的都是非守护线程。在 Java 中有一个规定:当所有的非守护线程退出后,整个 JVM 进程就会退出,守护线程是否正在运行不影响整个 JVM 进程的退出。例如,垃圾回收线程就是守护线程,它们在后台默默工作,当开发者的所有前台线程(非守护线程)都推出之后,整个 JVM 进程就退出了。

那么怎么设置一个线程为守护线程呢?那就是调用 setDaemon(true),在这样设置后,main 函数退出后,thread 线程也会退出,进而整个进程也会退出。

等待其他线程先执行 join()

这里的 join 即可以翻译为加入,也可以翻译为等待。具体来说,它的含义就是使当前线程等待调用 join 方法的线程执行完毕后再继续执行。这是一个非常有用的方法,可以协调线程的执行顺序,确保某些任务在其他任务完成之后才执行。

这个方法有三个版本:

public final void join() throws InterruptedException
public final void join(long millis) throws InterruptedException
public final void join(long millis, int nanos) throws InterruptedException
  • 无参数的版本:调用这个方法会让当前线程等待调用 join 方法的线程完成,即进入“等待状态”,直到目标线程终止。
  • 带超时参数的版本:这个版本允许当前线程等待一段时间(以毫秒为单位)后继续执行。如果目标线程在指定时间内没有完成,当前线程将继续执行。
  • 带超时和纳秒参数的版本:这个版本提供更精细的控制,可以指定等待时间的毫秒和纳秒部分。

以下是一个 join 的示例代码,展示这个方法的基本用法:

public static void main(String[] args) {
    System.out.println("主线程开始运行...");

    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("子线程开始运行...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程运行结束...");
        }
    });
    thread.start();

    try {
        thread.join();
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }

    System.out.println("主线程运行结束...");
}

如果不带入 join 方法的话,肯定是主线程执行完之后子线程再执行完。在加入了 join 方法后,线程的运行结果如下:

System.out                  I  主线程开始运行...
System.out                  I  子线程开始运行...
System.out                  I  子线程运行结束...
System.out                  I  主线程运行结束...

可见,主线程是加入了子线程后,主线程就进入了等待状态,等待子线程完成之后,主线程再继续执行。

被废弃的 stop()destroy()suspend()resume()

线程就是一段运行中的代码,或者说是一个运行中的函数,也就是 run 方法。既然是在运行中,就存在一个最基本的问题:运行到一半的线程能否强制杀死呢?

以前,Java 觉得是可以强制杀死的,于是便有了 stop()destroy()suspend()resume() 这些控制线程的生命周期的方法。但是现在,这些函数都已经是官方明确不建议使用了,都被标记为 @Deprecated。因为,如果强制杀死线程,则线程中所有使用的资源,例如文件描述符、网络连接等都不能正常关闭。

因为,一个线程一旦运行起来,就不要去强行去打断它,合理的关闭方法就是让其运行结束(也就是 run 方法执行完毕),干净地释放掉所有资源,然后退出。如果是一个不断循环运行的线程,就应该通过线程间的通信机制,让其他线程通知其退出。

上面的这4个函数,都是控制线程运行时的方法,基于上述原因,都早已经被废弃,所以就不再说明了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李斯维

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值