当前位置: 首页 > article >正文

【Java】线程相关面试题 (基础)

文章目录

  • 线程与进程区别
  • 并行与并发区别解析
      • 概念含义
      • 资源利用
      • 执行方式
      • 应用场景
  • 创建线程
  • 线程状态
  • 如何保证新建的三个线程按顺序执行
  • wait方法和sleep方法的不同
      • 所属类和使用场景
      • 方法签名和参数说明
      • 调用`wait`方法的前提条件
      • 被唤醒的方式
      • 与`notify`/`notifyAll`方法的协作
      • 使用示例
      • 注意事项
  • 停止线程的三种方式

线程与进程区别

在这里插入图片描述

  • 进程的定义与实例
    • 进程是当程序被运行,从磁盘加载程序代码到内存时开启的。例如打开谷歌浏览器或txt文档等程序就是开启了一个进程,在Windows中有多实例进程(可打开多份,如浏览器、txt文档)和单实例进程(如tears客户端、企业微信在系统层面只能打开一份)。
  • 线程的定义与作用
    • 线程包含指令,交给CPU运行。进程至少包含一到多个线程,每个线程执行不同任务。
  • 线程与进程的区别
    • 进程是正在运行的程序实例,包含多个线程执行不同任务。不同进程使用不同内存空间,而当前进程下的所有线程可以共享该进程的内存空间。线程更轻量,其上下文切换成本一般比进程上下文切换成本低。面试时主要回答这三点:进程和线程的关系、内存占用情况(强调进程下线程共享内存)、线程更轻量且切换成本低。

并行与并发区别解析

概念含义

  • 并行:同一时间动手做多件事情的能力。例如在多核CPU下,多个核心可以同时执行不同的线程,像四核CPU能同时执行四个线程,这些线程是真正意义上的同时进行
  • 并发:同一时间应对多件事情的能力。在单核CPU中,由于只有一个核心,多个线程不能同时执行,而是通过任务管理器分配时间片,轮流使用CPU,虽然每个时间片只有一个线程执行,但因CPU切换速度快,宏观上给人一种并行的感觉,微观上实际是串行执行。例如家庭主妇独自做饭、打扫卫生、给孩子喂奶,一个人轮流交替做这些事,就如同单核CPU处理多线程任务。

资源利用

  • 并行:需要多核CPU等硬件资源支持,每个核心可以独立运行一个线程,实现真正的同时处理多个任务,充分利用了多核CPU的计算能力,提高了整体任务处理效率。
  • 并发:主要依赖于操作系统的调度机制,在单核CPU环境下,通过合理分配时间片给不同线程,让多个任务看起来像是同时在处理,有效利用了单个CPU的时间资源,避免某个线程长时间占用CPU导致其他线程等待过久,但整体效率受限于单核CPU的处理能力。

执行方式

  • 并行:多个任务在多个处理器或多核CPU的不同核心上同时执行,任务之间相互独立,不存在资源竞争(除非访问共享资源时需要进行同步处理),执行顺序是真正意义上的同时进行。
  • 并发:多个任务在单核CPU上通过时间片轮转的方式交替执行,每个任务执行一段时间后暂停,切换到下一个任务,由于时间片很短,给用户造成任务在同时进行的错觉,但实际上在微观层面是串行执行的,任务之间可能存在频繁的上下文切换开销。

应用场景

  • 并行:适用于计算密集型任务,如大规模数据处理、复杂科学计算等,通过将任务分解到多个核心上同时计算,可以显著缩短计算时间,提高计算性能。例如在图像渲染、视频编码解码等领域,利用多核CPU并行处理不同部分的图像或视频数据,能快速完成处理工作。
  • 并发:常用于I/O密集型任务,如网络通信、文件读写等操作,这些任务在等待I/O操作完成时会阻塞线程,使用并发可以在等待一个任务的I/O操作时切换到其他任务执行,提高CPU利用率,避免线程长时间空闲等待。比如在一个Web服务器中,同时处理多个客户端的请求,每个请求在等待数据库查询或文件读取等I/O操作时,服务器可以切换去处理其他客户端请求,提高整体响应能力。

创建线程

  1. 创建线程的方式介绍

    • 创建线程共有四种方式,分别是继承Thread类、实现Runnable接口、实现Callable接口和使用线程池创建线程。
  2. 继承Thread类创建线程

    • 定义一个类继承Thread类,重写run方法,在run方法中编写线程要执行的代码。
    • 使用时先创建该类的对象,然后调用start方法开启线程,new两次对象相当于开两个线程。
  3. 实现Runnable接口创建线程

    • 定义一个类实现Runnable接口,重写run方法,该方法为线程执行的代码。
    • 使用时先创建类的对象,将其包装在Thread类中,再调用Thread对象的start方法开启线程,new两次对象相当于开两个线程。
  4. 实现Callable接口创建线程

    • 定义一个类实现Callable接口,重写call方法,call方法有返回值(通过泛型指定)且可抛异常,call方法的代码为线程要执行的逻辑。
    • 使用时先创建类的对象,配合FutureTask包装该对象,再将FutureTask包装在Thread类中,调用Thread对象的start方法开启线程,通过FutureTask的get方法获取线程执行后的返回值。
  5. 使用线程池创建线程

    • 创建一个类实现Runnable或Callable接口,编写线程执行逻辑。
    • 使用时先创建固定大小的线程池(线程池后期会详细讲解),通过线程池的submit方法提交任务(即实现接口的类的对象),线程池会自动执行线程中的逻辑。
  6. Runnable和Callable的区别

    • 返回值:Runnable接口的run方法无返回值,Callable接口的call方法有返回值且需配合FutureTask使用get方法获取返回值。
    • 异常处理:run方法不能抛异常,只能内部try - catch处理;call方法可以抛异常。
  7. start方法和run方法的区别

    • 功能:start方法用于启动线程,线程独立执行run方法中的代码;run方法是普通方法,直接调用如同调用普通方法,在当前线程顺序执行代码。
    • 调用次数:start方法只能被调用一次启动线程,多次调用会抛异常;run方法可多次调用。
  8. 总结

    • 创建线程有继承Thread类、实现Runnable接口、实现Callable接口和使用线程池四种方式,项目中一般使用线程池创建线程。
    • Runnable和Callable的区别主要在返回值、异常处理方面。
    • start方法用于启动线程且只能调用一次,run方法是普通方法可多次调用。

线程状态

  1. 线程状态面试题介绍
    • 状态定义:参考Thread类的内部枚举类State,定义了六个线程状态,即new(新建)、runnable(可运行)、block(阻塞)、waiting(等待)、time waiting(时间等待)、terminated(终结)。

在这里插入图片描述

  1. 线程状态及转换初步讲解
    • 新建状态:创建线程对象时进入,如创建线程t1和t2时。
    • 就绪与运行状态:调用线程方法后进入就绪状态,抢到CPU时间片才有执行权,线程运行完成后进入死亡状态。
    • 阻塞状态:线程加锁时,未获得锁的线程进入阻塞状态,获得到锁后转为可运行状态。
    • 等待状态:线程内部调用wait方法进入等待状态,其他线程调用notify或notifyAll方法唤醒后变为可运行状态。
    • 时间等待状态:线程调用sleep方法进入时间等待状态,时间结束后转为可运行状态。
  2. 总结线程状态及转换
    • 线程状态总结:包含六个状态,新建、可运行、阻塞、等待、计时等待、终止状态。
    • 状态转换关系梳理
      • 新建到可执行:创建线程对象为新建状态,调用方法后转换为可执行状态。
      • 可执行到终止:线程获取CPU执行权并执行结束后为终止状态。
      • 可执行状态的其他转换
        • 可执行到阻塞:未获取到锁(如synchronized或Lock锁)进入阻塞状态,获得到锁的执行权后切换为可执行状态。
        • 可执行到等待:调用wait方法进入等待状态,其他线程调用notify或notifyAll唤醒后切换为可执行状态。
        • 可执行到计时等待:调用sleep方法进入计时等待状态,时间到后切换为可执行状态。

文章目录

  • 线程与进程区别
  • 并行与并发区别解析
      • 概念含义
      • 资源利用
      • 执行方式
      • 应用场景
  • 创建线程
  • 线程状态
  • 如何保证新建的三个线程按顺序执行
  • wait方法和sleep方法的不同
      • 所属类和使用场景
      • 方法签名和参数说明
      • 调用`wait`方法的前提条件
      • 被唤醒的方式
      • 与`notify`/`notifyAll`方法的协作
      • 使用示例
      • 注意事项
  • 停止线程的三种方式


如何保证新建的三个线程按顺序执行

  • 方法介绍
    • 对于“如何保证新建t1、t2、t3三个线程按顺序执行”这一面试题,可使用线程中的“join方法”来解决。该方法的作用是等待线程运行结束,调用此方法的线程会被阻塞,进入time waiting(时间等待)状态,直到被调用“join方法”的线程执行完成后,调用者才能继续执行。
  • 代码演示
  • 在代码中创建了t1、t2、t3三个线程,在t2线程中调用了t1的“join方法”,这意味着t2线程想要运行必须等待t1线程结束;在t3线程中调用了t2的“join方法”,所以t3线程需等待t2线程运行结束后才能运行。启动线程的顺序不影响最终结果,最终会按t1、t2、t3的顺序执行。通过代码执行结果展示了t1先执行,完成后t2执行,t2执行完t3执行,从而验证了这种方法可保证线程按顺序执行。
  1. notify和notifyAll的区别

    为notify是只随机唤醒一个等待(wait)方法的线程,而notifyAll是唤醒所有等待方法的线程。


wait方法和sleep方法的不同

在这里插入图片描述

在Java中,wait方法用于使当前线程等待,直到其他线程调用该对象的notify方法或notifyAll方法唤醒它,或者等待一定的时间(如果指定了超时时间)。以下是关于wait方法的详细介绍:

所属类和使用场景

  1. 所属类wait方法属于Object类,这意味着Java中的任何对象都可以调用该方法。
  2. 使用场景:主要用于多线程编程中,实现线程之间的协作和同步。例如,当一个线程需要等待某个条件满足时,可以调用wait方法进入等待状态,直到其他线程改变了共享资源的状态并通知它。

方法签名和参数说明

  1. 方法签名public final void wait() throws InterruptedExceptionpublic final native void wait(long timeout) throws InterruptedException
  2. 参数说明
    • 无参的wait方法会使当前线程无限期等待,直到被唤醒。
    • 带参数的wait方法接受一个long类型的参数,表示等待的超时时间(以毫秒为单位)。如果在指定时间内没有被唤醒,线程会自动苏醒并继续执行。

调用wait方法的前提条件

  1. 当前线程必须拥有该对象的锁。也就是说,wait方法必须在同步代码块(synchronized块)中调用,否则会抛出IllegalMonitorStateException异常。
  2. 例如,以下代码演示了正确调用wait方法的方式:
synchronized (object) {
    // 当前线程获取了object对象的锁,可以调用wait方法
    object.wait();
}

被唤醒的方式

  1. 其他线程调用notify方法:唤醒在此对象监视器上等待的单个线程。如果有多个线程在等待,选择是任意的,由操作系统的调度策略决定。
  2. 其他线程调用notifyAll方法:唤醒在此对象监视器上等待的所有线程。被唤醒的线程将竞争重新获取对象的锁,然后继续执行。
  3. 等待超时:如果调用了带超时参数的wait方法,当超时时间到达时,线程会自动苏醒,继续执行后续代码。

notify/notifyAll方法的协作

  1. wait方法与notify/notifyAll方法必须在同一对象上调用,以实现线程之间的正确协作。
  2. 通常,一个线程在等待某个条件时调用wait方法,而另一个线程在改变条件后调用notifynotifyAll方法来唤醒等待的线程。

使用示例

以下是一个简单的示例,展示了wait方法和notify方法的基本用法:

public class WaitNotifyExample {
    public static void main(String[] args) {
        final Object lock = new Object();

        // 线程1:等待条件满足
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("线程1:开始等待");
                    lock.wait(); // 释放锁并等待
                    System.out.println("线程1:被唤醒,继续执行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 线程2:改变条件并通知线程1
        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程2:改变条件,并通知线程1");
                lock.notify(); // 唤醒等待的线程1
            }
        });

        thread1.start();
        try {
            Thread.sleep(1000); // 确保线程1先进入等待状态
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
    }
}

在上述示例中,线程1获取lock对象的锁后调用wait方法进入等待状态,同时释放锁。线程2获取lock对象的锁后调用notify方法唤醒线程1,线程1被唤醒后重新竞争锁,获取锁后继续执行后续代码。

注意事项

  1. 在使用wait方法时,必须在循环中调用,以避免虚假唤醒(spurious wakeup)的问题。虚假唤醒是指线程在没有被其他线程明确唤醒的情况下苏醒,可能是由于操作系统或JVM的内部原因。例如:
while (condition) {
    synchronized (object) {
        object.wait();
    }
}
  1. 调用wait方法的线程会释放对象的锁,但在被唤醒后重新竞争锁。如果多个线程同时竞争锁,唤醒顺序是不确定的,取决于操作系统的调度策略。
  2. wait方法会抛出InterruptedException异常,当线程在等待过程中被中断时,会抛出该异常。因此,在调用wait方法时,需要正确处理异常,以确保程序的稳定性和正确性。

在这里插入图片描述


文章目录

  • 线程与进程区别
  • 并行与并发区别解析
      • 概念含义
      • 资源利用
      • 执行方式
      • 应用场景
  • 创建线程
  • 线程状态
  • 如何保证新建的三个线程按顺序执行
  • wait方法和sleep方法的不同
      • 所属类和使用场景
      • 方法签名和参数说明
      • 调用`wait`方法的前提条件
      • 被唤醒的方式
      • 与`notify`/`notifyAll`方法的协作
      • 使用示例
      • 注意事项
  • 停止线程的三种方式


停止线程的三种方式

  1. 停止线程的三种方式
    • 使用退出标志:通过定义一个标志变量(如flag),在run方法中使用循环条件控制线程执行,当标志变量改变时,线程正常退出。例如,在my interrupt 1类中,run方法里使用while循环(while (!flag)),线程在循环内打印信息并睡眠3秒。主线程启动该线程后睡眠6秒,然后将flag改为true,使线程在6秒后正常退出。
    • 调用stop方法(不推荐)stop方法可以强行终止线程,但此方法已作废不推荐使用。
    • 调用interrupt方法:该方法包含两种情况。
      • 一是打断阻塞的线程(如处于sleepwaitjoin状态的线程),调用interrupt会抛出InterruptException异常;
      • 二是打断正常的线程,可根据线程的打断状态标记是否退出线程,与第一种使用退出标志的方式类似。
      • 在这里插入图片描述

http://www.kler.cn/a/456294.html

相关文章:

  • Linux第100步_Linux之设置LCD作为终端控制台和LCD背光调节
  • C# 中使用 MassTransit
  • 埃斯顿机器人程序案例多个点位使用变量
  • 梳理你的思路(从OOP到架构设计)_介绍Android的Java层应用框架05
  • 代码随想录算法训练营第六十天 | 图 | A星算法
  • [羊城杯 2024]Check in
  • 工业安全监测审计系统(源码+文档+部署+讲解)
  • 【我的 PWN 学习手札】IO_FILE 之 stdout任意地址读
  • 普通的树形数据primevue的treetable组件的treetable[ ]
  • android系统查找应用包名以及主activity:
  • WPF 绘制过顶点的圆滑曲线(样条,贝塞尔)
  • 创建用于预测序列的人工智能模型,用Keras Tuner探索模型的超参数。
  • PDF书籍《手写调用链监控APM系统-Java版》第8章 插件与链路的结合:Gson插件实现
  • Arcgis中python工具箱制造要点及统计要素图层字段信息工具分享
  • 【每日学点鸿蒙知识】组件封装通用方法、callback和await性能对比、Web组件下拉刷新、hsp包报错、WebView圆角
  • 使用 Three.js 创建一个 3D 人形机器人仿真系统
  • 基于MATLAB的冰箱水果保鲜识别系统
  • RustDesk内置ID服务器,Key教程
  • 洛谷 P2422:良好的感觉 ← 单调队列+前缀和
  • 【PPTist】表格功能
  • PyTorch框架——基于深度学习LYT-Net神经网络AI低光图像增强系统源码
  • 2.5.3 文件使用、共享、保护、安全与可靠性
  • 瑞芯微全新芯片平台RK3506优势详解,高集成低功耗,为工业而生 触觉智能测评
  • Proteus仿真——《51单片机AD和DA转换器的设计》
  • 【数据结构】单链表的使用
  • 【CSS in Depth 2 精译_096】16.4:CSS 中的三维变换 + 16.5:本章小结