面试官:如何判断线程是否已经启动?

2024-06-21 08:50:00 浏览数 (2)

你好,我是

一位朋友在面试中被问到:如何判断线程是否已经启动?

不想一开始就给出答案,而是逐步分析问题,掌握好知识的来龙去脉,不要把知识学的那么枯燥无味,不然你迟早要被劝退。

线程与进程

我们快速简单地了解一下线程与进程。

进程(Process)

定义 :进程是程序的一次执行过程,是系统资源分配的基本单位,每个进程之间相互独立。

特点

  • 拥有独立的内存空间和系统资源。
  • 可以包含多个线程,即多个线程共享进程的资源。
  • 进程之间通信相对复杂。

生活案例 :想象你在一台桌面电脑上打开了多个软件程序,比如同时运行着一个浏览器、一个文字处理软件以及一个视频播放器,每个软件就是一个独立的进程。每个软件有自己的内存空间和资源,彼此之间独立运行。

线程(Thread):

定义 :线程是进程内的执行单元,是 CPU 调度的基本单位,多个线程共享进程的资源。

特点

  • 共享进程的地址空间和系统资源。
  • 各个线程之间可以方便、快速地共享数据和通信。
  • 线程之间的切换较进程更快。

生活案例 :想象你在早晨做早饭的时候,同时负责煎蛋、煮咖啡和煮面,这就好比在一个进程中有多个线程在并行执行不同的任务。每个任务可以看作是一个线程,它们共享厨房这个整体资源。

不要一味地学技术以及背面试题,我们要想办法让技术回归生活案例中

总结
  • 进程是程序的一次执行,拥有独立的资源;线程是进程内的执行单元,共享进程的资源。
  • 进程之间相互独立,通信需要额外的机制;线程之间共享进程资源,通信更加方便快捷。
  • 操作系统负责进程调度和资源分配,而线程是在进程内部由程序员控制的。

线程创建方式

估计你也看过很多文章讲线程的创建方式有3种,4种,5种,6种,7种....。

不是说的种类越多显得你越牛X,而是要知道每一种的特点以及使用场景那才是真材实料。

我这里说三种,不想讲太多,没有多大意义。

1. 继承 Thread 类方式 :通过创建类并继承 Thread 类,重写 run() 方法来定义线程的执行逻辑。

代码语言:javascript复制
// 继承 Thread 类创建线程
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread is running");
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建并启动线程
        MyThread thread = new MyThread();
        thread.start();
    }
}

这里有个很大的问题,那就是Java只支持单继承,为了解决单继承的问题咱们可以使用实现Runnable接口来创建线程。

2. 实现 Runnable 接口方式 :通过创建类实现 Runnable 接口,实现 run() 方法来定义线程的执行逻辑。然后将实现了 Runnable 接口的对象传递给 Thread 类的构造方法。

代码语言:javascript复制
// 实现 Runnable 接口创建线程
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable running");
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建线程并传入实现了 Runnable 接口的对象
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}

单继承的问题解决了,可是他们两都有个共同的问题:没有返回值

为此,我们可以使用实现Callable 接口来解决。

3.实现Callable接口 :使用 Callable 接口配合 Future 和 ExecutorService 来创建线程并返回结果。

代码语言:javascript复制
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

// 实现 Callable 接口创建线程
class MyCallable implements Callable<String> {
    @Override
    public String call() {
        return "Callable thread is running";
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(1);

        // 提交 Callable 任务并获取 Future 对象
        Future<String> future = executor.submit(new MyCallable());

        try {
            // 获取线程执行结果
            String result = future.get();
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 关闭线程池
        executor.shutdown();
    }
}

通过实现 Callable 接口并重写 call() 方法来定义线程的执行逻辑。然后使用 ExecutorService 创建线程池并提交 Callable 任务,得到一个 Future 对象,通过该对象可以获取线程执行结束后的返回结果。

都是一环扣一环的,先是什么问题,解决了什么问题后,还是存在什么问题,又是怎么解决的。有点类似于我们做项目开发,很多时候我们的项目需要重构、拆分以及合并,都是为了解决某些问题。

线程状态

在我们Java语言中,线程的状态主要由 Thread.State 枚举类定义。这些状态描述了线程在其生命周期中可能经历的不同阶段。以下是 Java 中线程的几种状态:

NEW(新建) :当线程对象被创建但还未启动时,线程处于 NEW 状态。

RUNNABLE(可运行) :线程在操作系统中处于可运行状态,等待被调度执行。

BLOCKED(阻塞) :线程因为等待监视器锁(synchronized 关键字)而被阻塞。

WAITING(等待) :线程在等待特定条件触发的情况下进入等待状态,例如调用 Object.wait() 方法。

TIMED_WAITING(计时等待) :线程在有超时限制的情况下等待,例如调用带有超时参数的 Object.wait(timeout) 或 Thread.sleep(milliseconds) 方法。

TERMINATED(终止) :线程执行完毕或因异常退出时,线程处于终止状态。

在 Java 中,通过调用 getState() 方法可以获取线程的当前状态,返回的是 Thread.State 枚举类型。

这里需要注意:NEW状态时,说明线程还未启动,其他状态都表示线程已经启动了或启动后执行结束了。

线程常见方法

Thread 类提供了一些常用的方法来管理线程的执行和控制。以下是 Thread 类常见方法及其作用:

start() :启动线程,使线程进入就绪(runnable)状态,当获取到 CPU 时间片时开始执行线程的 run() 方法。

run() :线程的执行逻辑,需要在子类中重写这个方法来定义线程的任务。

sleep(long millis) :让当前线程休眠指定的时间(以毫秒为单位),线程进入阻塞状态,不会释放锁。

join() :等待调用该方法的线程执行完毕,当前线程会被阻塞,直到目标线程执行完毕。

interrupt() :中断线程,给该线程发出一个中断信号,线程可以在合适的时间响应中断。isInterrupted() 和 interrupted():

isInterrupted() :检查当前线程是否被中断,不会清除中断状态。

interrupted() :静态方法,检查当前线程是否被中断,会清除中断状态。

yield() :暂停当前正在执行的线程,让 CPU 调度器重新选择其他线程执行,可能会提高其他线程的执行机会。

isAlive() :检查线程是否存活,即线程已经启动但尚未终止,返回 boolean 值。

setName(String name) 和 getName()

  • setName(String name):设置线程的名称。
  • getName():获取线程的名称。

setPriority(int priority) 和 getPriority()

  • setPriority(int priority):设置线程的优先级,范围是 1 到 10,默认是 5。
  • getPriority():获取线程的优先级。

getState() :返回线程状态

关于线程的常见方法,很多人都不太关注,这可是有很多朋友面试被问到过的。

线程是否已经启动

聊了那么多线程相关知识,终于来到了今天的话题:线程是否已经启动?

下面,我用三种方式来和大家探讨。

第一种:使用Thread.currentThread().isAlive() 方法

Thread.currentThread().isAlive()方法用于检查当前线程是否处于活动状态。如果线程已经启动且尚未终止,则返回true,否则返回false。

代码语言:javascript复制
Thread thread = new Thread(() -> {
    // 线程执行的任务
});

// 判断线程是否启动
if (thread.isAlive()) {
    System.out.println("线程已启动");
} else {
    System.out.println("线程未启动");
}
第二种:使用 getState() 方法

通过调用getState()方法可以获取线程的状态,常见的状态包括NEW(新建)、RUNNABLE(可运行)、TIMED_WAITING(计时等待)等。

代码语言:javascript复制
Thread thread = new Thread(() -> {
    // 线程执行的任务
});

// 获取线程状态
Thread.State state = thread.getState();

if (state == Thread.State.NEW) {
    System.out.println("线程尚未启动");
} else {
    System.out.println("线程已启动");
}
第三种:使用 布尔变量或状态标记

在线程类中添加一个布尔变量或状态标记,来表示线程是否已经启动或结束。

代码语言:javascript复制
class MyThread extends Thread {
    private boolean isStarted = false;

    @Override
    public void run() {
        isStarted = true;
        // 执行任务
    }

    public boolean isThreadStarted() {
        return isStarted;
    }
}

MyThread thread = new MyThread();
thread.start();

// 判断线程是否启动
if (thread.isThreadStarted()) {
    System.out.println("线程已启动");
} else {
    System.out.println("线程未启动");
}

总结

本文从线程与进程开始聊,再聊到了线程的创建方式、线程的状态、线程常见方法,最后再来聊了如何判断线程是否已经启动。希望这种思路能让你更轻松的掌握这个问题,另外,强烈建议在学技术时,最好是把技术回归到我们生活案例中。

0 人点赞