Java多线程基础作者没有四次元口袋的蓝胖日期2026-06-17标签Java, 多线程一、线程与进程进程操作系统分配资源的基本单位每个正在运行的程序就是一个进程。线程CPU调度的基本单位一个进程内可以有多个线程它们共享进程的内存资源。对比项进程线程资源独立内存空间共享进程内存开销创建/切换成本高创建/切换成本低通信需要IPC机制直接共享变量需同步崩溃影响互不影响一个线程崩可能拖垮整个进程⚡面试坑点面试官问进程和线程的区别别只背概念——要提到线程共享内存带来的并发安全问题这才是后续synchronized、Lock等知识的基础。二、创建线程的三种方式2.1 继承Thread类classMyThreadextendsThread{Overridepublicvoidrun(){System.out.println(线程运行Thread.currentThread().getName());}}// 使用MyThreadtnewMyThread();t.start();// 注意是start()不是run()要点继承Thread类重写run()方法调用start()启动线程JVM会自动调用run()Java单继承已经继承了其他类就不能再用这种方式2.2 实现Runnable接口classMyRunnableimplementsRunnable{Overridepublicvoidrun(){System.out.println(线程运行Thread.currentThread().getName());}}// 使用方式1传入Thread构造器ThreadtnewThread(newMyRunnable());t.start();// 使用方式2Lambda简化Runnable是函数式接口newThread(()-{System.out.println(Lambda线程运行);}).start();要点实现Runnable接口实现run()方法避免了单继承限制更灵活可以用Lambda表达式简化写法推荐方式也是实际开发中最常用的2.3 实现Callable接口classMyCallableimplementsCallableString{OverridepublicStringcall()throwsException{returnCallable返回结果;}}// 使用配合FutureTaskFutureTaskStringfutureTasknewFutureTask(newMyCallable());ThreadtnewThread(futureTask);t.start();// 获取返回值会阻塞直到线程执行完StringresultfutureTask.get();System.out.println(result);要点实现CallableV接口重写call()方法call()可以有返回值可以抛出异常必须配合FutureTask使用FutureTask同时实现了Runnable和Futureget()方法会阻塞一般放在最后调用三种方式对比对比项继承Thread实现Runnable实现Callable返回值无无有泛型异常不能抛出checked异常不能抛出checked异常可以抛出继承限制单继承无无复用性差每次new一个Thread好同一个Runnable可传给多个Thread好复杂度最简单简单稍复杂推荐度⭐⭐⭐⭐⭐⭐需要返回值时用⚡面试高频题三种创建线程的方式有什么区别回答要点继承限制、返回值、异常处理、实际开发中推荐Runnable因为线程池接收的就是Runnable/Callable加分项提到线程池才是生产环境的标准用法三种方式只是基础⚡面试坑点调用run()和start()有什么区别start()启动新线程JVM调用run()run()只是普通方法调用在当前线程执行不会创建新线程同一个Thread对象start()只能调用一次重复调用会抛IllegalThreadStateException三、线程常见API3.1 获取和设置线程名称ThreadtnewThread(()-{},我的线程);// 构造时命名t.setName(线程A);// 设置名称Stringnamet.getName();// 获取名称StringmainNameThread.currentThread().getName();// 当前线程名称线程命名规则main线程叫main默认线程名Thread-0、Thread-1、Thread-2…建议给线程起有意义的名字方便排查问题3.2 线程休眠Thread.sleep(1000);// 休眠1000毫秒1秒要点sleep()是静态方法让当前线程休眠休眠期间不释放锁这是和wait()的重要区别时间到后进入就绪态不一定是马上执行InterruptedException必须处理3.3 线程让步Thread.yield();// 礼让线程要点静态方法提示线程调度器当前线程愿意让出CPU只是建议不保证生效实际开发中很少用3.4 线程优先级t.setPriority(Thread.MAX_PRIORITY);// 10t.setPriority(Thread.MIN_PRIORITY);// 1t.setPriority(Thread.NORM_PRIORITY);// 5默认要点优先级范围1-10默认5优先级只是建议不保证高优先级一定先执行不同操作系统的调度策略不同不要依赖优先级⚡面试坑点sleep()和wait()的区别sleep()是Thread的静态方法wait()是Object的实例方法sleep()不释放锁wait()释放锁sleep()到时间自动唤醒wait()需要notify()/notifyAll()唤醒sleep()可以在任何地方调用wait()必须在同步块中调用四、守护线程4.1 什么是守护线程守护线程Daemon Thread为其他线程提供服务的后台线程当所有非守护线程结束后JVM会退出守护线程也会随之销毁。4.2 使用方式ThreaddaemonnewThread(()-{while(true){System.out.println(守护线程运行中...);try{Thread.sleep(500);}catch(InterruptedExceptione){}}});daemon.setDaemon(true);// 必须在start()之前设置daemon.start();4.3 典型应用GC线程JVM的垃圾回收器就是守护线程监控线程后台监控、日志采集心跳线程保持连接活跃4.4 注意事项setDaemon(true)必须在start()之前调用否则抛IllegalThreadStateException守护线程中finally块不一定执行JVM退出时守护线程直接终止守护线程创建的子线程默认也是守护线程⚡面试坑点守护线程的finally块一定会执行吗不一定当所有用户线程结束JVM退出时守护线程直接被终止finally可能来不及执行所以不要在守护线程中做需要保证完成的操作如关闭资源、写文件等五、join的使用5.1 join是什么join()等待调用该方法的线程执行完毕再继续执行当前线程。简单说就是线程插队。5.2 使用方式Threadt1newThread(()-{System.out.println(t1执行...);});Threadt2newThread(()-{try{t1.join();// t2等t1执行完再继续}catch(InterruptedExceptione){}System.out.println(t2执行...);});t1.start();t2.start();// 输出t1执行... → t2执行...5.3 join的重载方法方法说明join()等待线程执行完毕join(long millis)最多等待millis毫秒join(long millis, int nanos)最多等待millis毫秒nanos纳秒5.4 典型场景// 场景主线程等待所有子线程完成后再汇总结果Threadt1newThread(task1);Threadt2newThread(task2);t1.start();t2.start();t1.join();// 等t1执行完t2.join();// 等t2执行完System.out.println(所有任务完成开始汇总);⚡面试坑点join()的底层实现是什么底层用的是wait()在join的线程存活期间不断wait(0)当被join的线程执行完毕即isAlive()返回false自动notifyAll()所以join()会释放锁因为底层是wait()⚡面试坑点join()和sleep()的区别join()等待另一个线程结束sleep()只让当前线程休眠固定时间join()底层是wait()会释放锁sleep()不释放锁join()是实例方法sleep()是静态方法六、interrupt的使用6.1 为什么需要interruptJava没有安全的方式强制停止线程stop()已废弃会导致数据不一致所以采用协作式中断一个线程请求另一个线程中断由被请求的线程自行决定如何响应。6.2 三个核心方法方法说明interrupt()中断线程设置中断标志位为trueisInterrupted()判断线程是否被中断不清除标志位static interrupted()判断当前线程是否被中断清除标志位6.3 使用场景场景1打断正在运行的线程ThreadtnewThread(()-{while(!Thread.currentThread().isInterrupted()){// 正常执行任务System.out.println(工作中...);}System.out.println(收到中断信号优雅退出);});t.start();// 2秒后请求中断Thread.sleep(2000);t.interrupt();场景2打断处于阻塞状态的线程sleep/wait/joinThreadtnewThread(()-{try{Thread.sleep(10000);// 休眠10秒}catch(InterruptedExceptione){// sleep/wait/join被interrupt时会抛InterruptedException// 同时清除中断标志位System.out.println(被中断了中断标志位Thread.currentThread().isInterrupted());// 输出被中断了中断标志位false}});t.start();Thread.sleep(1000);t.interrupt();6.4 关键注意事项interrupt()不会强制停止线程只是设置标志位阻塞中的线程被中断会抛InterruptedException同时清除标志位捕获InterruptedException后如果想保持中断状态需要再次调用interrupt()catch(InterruptedExceptione){Thread.currentThread().interrupt();// 重新设置中断标志// 然后决定是退出还是继续}不要吞掉InterruptedException——要么重新抛出要么重新设置中断标志⚡面试高频题如何优雅地停止一个线程推荐方式用interrupt()检查中断标志位不推荐stop()已废弃不安全、suspend()已废弃容易死锁其他方式用volatile boolean标志位但不能打断阻塞状态加分项对比interrupt和volatile标志位的区别——interrupt能打断sleep/wait/join等阻塞volatile标志位不能⚡面试坑点isInterrupted()和interrupted()的区别isInterrupted()实例方法不清除中断标志位interrupted()静态方法清除中断标志位调用interrupted()后再调用isInterrupted()返回false七、线程的生命周期7.1 六种状态Java线程在Thread.State枚举中定义了6种状态状态说明触发条件NEW新建new Thread()后还没调用start()RUNNABLE可运行调用start()后包含就绪和运行中BLOCKED阻塞等待获取synchronized锁WAITING等待wait()/join()/LockSupport.park()无限期等待TIMED_WAITING计时等待sleep(ms)/wait(ms)/join(ms)有时间限制的等待TERMINATED终止run()方法执行完毕或抛出未捕获异常7.2 状态转换图start() NEW ──────────→ RUNNABLE ←─────────────────────┐ │ ↑ │ │ │ 获取到锁 │ │ │ │ ↓ │ │ BLOCKED等待synchronized锁 │ │ │ │ 获取到锁 │ ↓ │ RUNNABLE │ │ │ │ │ │ wait()/join() │ │ ↓ │ │ WAITING ───── notify()/notifyAll() ──→ RUNNABLE │ │ │ │ │ sleep(ms)/wait(ms)/join(ms) │ ↓ │ │ TIMED_WAITING ── 超时/notify() ──→ RUNNABLE │ │ │ run()执行完毕/异常 │ ↓ │ TERMINMINATED ←──────────────────────┘7.3 关键转换说明NEW → RUNNABLE调用start()RUNNABLE → BLOCKED线程尝试进入synchronized代码块/方法但锁被其他线程持有RUNNABLE → WAITINGObject.wait()无参Thread.join()无参LockSupport.park()RUNNABLE → TIMED_WAITINGThread.sleep(ms)Object.wait(ms)Thread.join(ms)LockSupport.parkNanos()BLOCKED → RUNNABLE获取到synchronized锁WAITING → RUNNABLEnotify()/notifyAll()唤醒从wait()被等待的线程执行完毕从join()LockSupport.unpark()TIMED_WAITING → RUNNABLE超时自动唤醒或被notify()等提前唤醒RUNNABLE → TERMINATEDrun()正常结束或抛出未捕获异常7.4 常见混淆点⚡面试坑点1RUNNABLE包含就绪和运行中两个状态吗是的Java线程的RUNNABLE对应操作系统线程的就绪Ready和运行Running因为线程是否正在使用CPU由操作系统调度Java层面无法区分所以Java没有把就绪和运行中拆成两个状态⚡面试坑点2BLOCKED和WAITING的区别BLOCKED等待获取synchronized锁被动阻塞WAITING主动等待调用了wait()/join()等等待某个条件或另一个线程BLOCKED是锁竞争导致WAITING是主动让出CPULock.lock()导致的等待不在BLOCKED状态而是WAITINGLockSupport.park⚡面试坑点3调用sleep()后线程进入什么状态TIMED_WAITING不是BLOCKEDsleep不释放锁但线程本身处于计时等待状态很多人会误答RUNNABLE或BLOCKED八、思维导图速览Java多线程基础 ├── 线程 vs 进程 │ ├── 进程资源分配单位独立内存 │ ├── 线程CPU调度单位共享内存 │ └── 核心共享内存→并发安全问题 │ ├── 创建线程三种方式 │ ├── 继承Thread简单单继承限制 │ ├── 实现Runnable推荐无继承限制可Lambda │ └── 实现Callable有返回值配FutureTask │ └── ⚡ start() vs run()start启动新线程run普通调用 │ ├── 常见API │ ├── getName/setName线程命名 │ ├── sleep(ms)休眠不释放锁 │ ├── yield()让步只是建议 │ └── setPriority()优先级不保证 │ ├── 守护线程 │ ├── setDaemon(true)start()前设置 │ ├── 所有用户线程结束→JVM退出→守护线程销毁 │ └── ⚡ finally不一定执行 │ ├── join() │ ├── 等待目标线程执行完 │ ├── 底层是wait()会释放锁 │ └── ⚡ vs sleepjoin等别的线程sleep等时间 │ ├── interrupt() │ ├── interrupt()设置中断标志 │ ├── isInterrupted()检查不清标志 │ ├── interrupted()检查清标志静态方法 │ ├── 阻塞中被interrupt→抛InterruptedException清标志 │ └── ⚡ 优雅停止线程的标准方式 │ └── 线程生命周期6种状态 ├── NEW → start() → RUNNABLE ├── RUNNABLE ↔ BLOCKEDsynchronized锁 ├── RUNNABLE ↔ WAITINGwait/join/park ├── RUNNABLE ↔ TIMED_WAITINGsleep/wait(ms)/join(ms) └── RUNNABLE → TERMINATEDrun结束/异常写在最后start()和run()的区别几乎是100%会问的必须脱口而出三种创建方式要能对比出优劣特别是为什么推荐Runnable线程生命周期6种状态要能画出来BLOCKED和WAITING的区别是常见追问interrupt机制是进阶内容能说明白协作式中断这个概念很加分守护线程的finally不保证执行——这是个反直觉的坑点记住就是赚到