Java后端面试必看|多线程基础(Thread/Runnable/线程状态)+ 实战,小白也能懂

作为Java后端程序员,「多线程」绝对是初级面试里的高频考点——几乎所有校招、1-2年经验的面试,都会问到:Thread和Runnable有啥区别?线程有哪些状态?怎么手动创建一个线程?

很多小白被问懵,不是因为知识点难,而是分不清“理论”和“实战”,要么记混状态流转,要么写不出简单的实战代码。

今天就用「通俗讲解+实战案例+面试避坑」的方式,把多线程基础吃透,看完直接能应对面试,也能上手写demo!

(文末附面试高频提问总结,建议收藏备用✨)

一、先搞懂:为什么需要多线程?

在讲Thread和Runnable之前,先想一个简单的问题:我们为什么要用多线程?

举个生活中的例子:你煮泡面的时候,不需要一直盯着锅——可以同时烧开水、拿调料包、洗杯子,几件事并行做,节省总时间。

Java程序也是一样:单线程程序是排队做事,一次只能执行一个任务,效率很低;而多线程能让多个任务并行执行(比如同时处理多个用户请求、异步处理日志),提升程序的执行效率和响应速度。

这也是后端开发中,多线程的核心作用:提升程序并发能力,优化执行效率

二、核心知识点:Thread vs Runnable(面试必问)

创建Java线程,最常用的两种方式就是「继承Thread类」和「实现Runnable接口」。两者的区别,是初级面试的重中之重,记住一句话:Runnable是任务,Thread是线程载体

1. 方式一:继承Thread

Thread类是Java提供的「线程类」,继承它之后,重写run()方法,就能定义线程要执行的任务;调用start()方法,就能启动线程。

实战代码(直接复制可运行):

    // 1. 继承Thread类
    class MyThread extends Thread {
        // 重写run()方法:定义线程要执行的任务
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                // Thread.currentThread().getName():获取当前线程名称
                System.out.println(Thread.currentThread().getName() 
                + ":执行任务" + i);
                try {
                    // 线程休眠100毫秒,模拟任务执行耗时
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    // 测试类
    public class ThreadDemo {
        public static void main(String[] args) {
            // 2. 创建线程对象
            MyThread thread1 = new MyThread();
            MyThread thread2 = new MyThread();
    
            // 3. 给线程设置名称(便于区分)
            thread1.setName("线程A");
            thread2.setName("线程B");
    
            // 4. 启动线程(注意:不能调用run()方法,否则就是单线程)
            thread1.start();
            thread2.start();
        }
    }

    运行结果(重点观察):

    线程A和线程B会交替执行,不会一直是A执行完再执行B——这就是多线程的并行效果(实际是CPU快速切换,看起来并行)。

    ⚠️ 面试避坑:启动线程必须用start()方法,不能用run()!调用run()方法,本质还是主线程在执行,不会开启新线程。

    2. 方式二:实现Runnable接口

    Runnable接口是Java提供的「任务接口」,里面只有一个抽象方法run(),用来定义线程要执行的任务。

    它的核心优势:解决Java单继承的局限性(一个类继承了Thread,就不能继承其他类,但可以实现多个接口)。

    实战代码(直接复制可运行):

      // 1. 实现Runnable接口,定义任务
      class MyRunnable implements Runnable {
          @Override
          public void run() {
              for (int i = 0; i < 5; i++) {
                  System.out.println(Thread.currentThread().getName() 
                  + ":执行任务" + i);
                  try {
                      Thread.sleep(100);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
      // 测试类
      public class RunnableDemo {
          public static void main(String[] args) {
              // 2. 创建任务对象(不是线程对象)
              MyRunnable task = new MyRunnable();
      
              // 3. 创建线程对象,把任务传入Thread(线程载体承载任务)
              Thread thread1 = new Thread(task, "线程C");
              Thread thread2 = new Thread(task, "线程D");
      
              // 4. 启动线程
              thread1.start();
              thread2.start();
          }
      }

      运行结果和继承Thread类类似,线程C和线程D交替执行。

      3. ThreadRunnable核心区别(面试必背)

      用表格总结,清晰好记,面试直接说:

      对比维度

      Thread

      Runnable接口

      本质

      线程类,本身承载线程和任务

      任务接口,只定义任务,不承载线程

      继承限制

      单继承,不能继承其他类

      无继承限制,可实现多个接口

      资源共享

      难实现(需静态变量)

      易实现(多个线程共用一个任务对象)

      推荐度

      低(有继承限制)

      高(灵活、无限制)

      补充:实际开发中,我们几乎不用继承Thread类,都是用Runnable接口(或Lambda表达式简化,下文实战会讲),因为它更灵活,也符合单一职责设计原则(任务和线程分离)。

      三、核心考点:线程的5种状态(图文+流转,记牢不混)

      线程从创建到销毁,会经历5种状态,这是面试高频提问(比如:线程有哪些状态?sleepwait会让线程进入什么状态?),结合图文理解,不用死记硬背。

      1. 线程的5种状态(Java官方定义)

      用通俗的语言解释,结合生活场景,一看就懂:

      新建状态(New:创建了线程对象,但还没调用start()方法(比如new Thread()后,线程就是新建状态,相当于泡面刚放进碗里,还没加水)。

      就绪状态(Runnable:调用了start()方法后,线程等待CPU调度(相当于泡面对好了,等你拿起叉子吃CPU就是,调度就是拿起叉子)。

      运行状态(RunningCPU调度到该线程,线程执行run()方法里的任务(相当于你正在吃泡面)。

      阻塞状态(Blocked:线程暂时停止执行,等待某个条件满足后,再回到就绪状态(相当于吃泡面中途,接了个电话,暂停吃面,挂了电话再继续吃)。

      死亡状态(Terminated:线程执行完run()方法,或被中断、异常终止(相当于泡面吃完了,碗空了,线程彻底结束,不能再启动)。

      2. 线程状态流转图(面试画出来,加分!)

      核心流转路径(记准,不要乱):

      新建状态(New → 就绪状态(Runnable)【调用start() → 运行状态(Running)【CPU调度】 → 阻塞状态(Blocked)【sleep/wait/同步锁】 → 就绪状态(Runnable)【阻塞条件解除】 → 运行状态(Running → 死亡状态(Terminated)【任务执行完/异常】

      ⚠️ 重点提醒:

      线程一旦进入死亡状态,就不能再调用start()方法重启(相当于泡面吃完了,不能再泡一次)。

      sleep()wait()的区别(面试常问):sleep()会让线程进入阻塞状态,但不会释放锁;wait()会让线程进入阻塞状态,且会释放锁(后续会专门讲,先记核心)。

      四、实战升级:用Lambda简化Runnable,模拟真实场景

      初级面试中,除了让你写ThreadRunnabledemo,还可能让你模拟多线程处理任务(比如:多线程下载文件、多线程统计数据)。

      下面用Lambda表达式简化RunnableJava 8+特性,面试写出来更加分),模拟「多线程处理用户请求」的场景,实战代码可直接运行、直接套用。

      实战场景:3个线程同时处理3个用户请求

        public class LambdaThreadDemo {
            public static void main(String[] args) {
                // 用Lambda表达式简化Runnable,不用单独写实现类
                // 线程1:处理用户A的请求
                Thread threadA = new Thread(() -> {
                    System.out.println("用户A请求处理中...");
                    try {
                        // 模拟处理耗时200毫秒
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("用户A请求处理完成✅");
                }, "处理线程1");
        
                // 线程2:处理用户B的请求
                Thread threadB = new Thread(() -> {
                    System.out.println("用户B请求处理中...");
                    try {
                        Thread.sleep(150);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("用户B请求处理完成✅");
                }, "处理线程2");
        
                // 线程3:处理用户C的请求
                Thread threadC = new Thread(() -> {
                    System.out.println("用户C请求处理中...");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("用户C请求处理完成✅");
                }, "处理线程3");
        
                // 启动所有线程
                threadA.start();
                threadB.start();
                threadC.start();
            }
        }

        运行结果分析:

        三个线程会同时启动,处理请求的顺序不确定(因为CPU调度随机),但最终都会执行完任务——这就是真实后端开发中,多线程处理并发请求的简单模拟。

        面试加分点:能说出多线程处理请求,能提升系统响应速度,避免单个请求耗时过长,导致其他请求阻塞

        五、面试高频提问总结(背会直接应对面试)

        整理了初级面试中,多线程基础的5个高频问题,答案直接记,不用再翻书:

        1.QThreadRunnable的区别?
        A① Thread是线程类,承载线程和任务;Runnable是任务接口,只定义任务。② Thread有单继承限制,Runnable无限制。③ 推荐用Runnable,更灵活、易实现资源共享。

        2.Q:启动线程用start()还是run()?为什么?
        A:用start();因为start()会开启新线程,让线程进入就绪状态,等待CPU调度;而run()只是主线程调用的普通方法,不会开启新线程。

        3.QJava线程有哪些状态?
        A:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Terminated),共5种。

        4.Q:线程进入阻塞状态的常见场景?
        A① 调用sleep()方法;② 调用wait()方法;③ 等待同步锁(synchronized);④ 等待IO操作(比如文件读取)。

        5.Q:线程死亡后,还能重启吗?
        A:不能;线程进入死亡状态(任务执行完/异常终止),就不能再调用start()方法重启,只能重新创建线程对象。

        最后总结

        多线程基础,是Java后端面试的敲门砖”——初级面试不会问太难的源码,重点考察你对「Thread/Runnable区别」「线程状态」「实战代码」的掌握。

        今天的内容,从基础讲解到实战案例,再到面试总结,覆盖了所有初级考点,建议大家把实战代码复制到IDE里运行一遍,亲手感受多线程的执行效果,比死记硬背更有效。

        📌 收藏本文,面试前快速过一遍,轻松应对多线程基础提问!

        评论区留言:你面试时,被多线程问过哪些问题?一起交流避坑~

        评论
        添加红包

        请填写红包祝福语或标题

        红包个数最小为10个

        红包金额最低5元

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

        打赏作者

        BlueSea 每日coding

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

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

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

        打赏作者

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

        抵扣说明:

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

        余额充值