Cyan Blog Cyan Blog
首页
  • Java (opens new window)
  • JUC (opens new window)
  • JVM (opens new window)
  • Redis

    • Redis安装 (opens new window)
    • Redis基础 (opens new window)
    • Redis实战 (opens new window)
    • Redis集群安装 (opens new window)
    • Redis分布式缓存 (opens new window)
    • Redis多级缓存 (opens new window)
    • Redis原理 (opens new window)
  • 管理工具

    • Maven (opens new window)
    • Git (opens new window)
  • SSM

    • Spring (opens new window)
    • SpringBoot (opens new window)
    • Mybatis (opens new window)
    • MybatisPlus (opens new window)
  • 微服务

    • Docker (opens new window)
    • RabbitMQ (opens new window)
    • SpringCloud (opens new window)
    • Dubbo (opens new window)
    • MongoDB (opens new window)
    • Zookeeper (opens new window)
  • Java面试题 (opens new window)
  • JUC面试题 (opens new window)
  • JVM面试题 (opens new window)
  • Linux面试题 (opens new window)
  • SQL面试题 (opens new window)
  • Maven面试题 (opens new window)
  • Redis面试题 (opens new window)
  • SSM面试题 (opens new window)
  • SpringCloud面试题 (opens new window)
  • Linux (opens new window)
  • C++ (opens new window)
  • 数据库

    • MySQL (opens new window)
    • NoSQL (opens new window)
  • 软件测试

    • 软件测试 (opens new window)
  • 加密解密 (opens new window)
  • bilibili字幕提取 (opens new window)
  • 道理 (opens new window)
  • 关于博主

    • Github (opens new window)
    • CSDN (opens new window)
  • 关于本站

    • 如何搭建博客网站 (opens new window)
首页
  • Java (opens new window)
  • JUC (opens new window)
  • JVM (opens new window)
  • Redis

    • Redis安装 (opens new window)
    • Redis基础 (opens new window)
    • Redis实战 (opens new window)
    • Redis集群安装 (opens new window)
    • Redis分布式缓存 (opens new window)
    • Redis多级缓存 (opens new window)
    • Redis原理 (opens new window)
  • 管理工具

    • Maven (opens new window)
    • Git (opens new window)
  • SSM

    • Spring (opens new window)
    • SpringBoot (opens new window)
    • Mybatis (opens new window)
    • MybatisPlus (opens new window)
  • 微服务

    • Docker (opens new window)
    • RabbitMQ (opens new window)
    • SpringCloud (opens new window)
    • Dubbo (opens new window)
    • MongoDB (opens new window)
    • Zookeeper (opens new window)
  • Java面试题 (opens new window)
  • JUC面试题 (opens new window)
  • JVM面试题 (opens new window)
  • Linux面试题 (opens new window)
  • SQL面试题 (opens new window)
  • Maven面试题 (opens new window)
  • Redis面试题 (opens new window)
  • SSM面试题 (opens new window)
  • SpringCloud面试题 (opens new window)
  • Linux (opens new window)
  • C++ (opens new window)
  • 数据库

    • MySQL (opens new window)
    • NoSQL (opens new window)
  • 软件测试

    • 软件测试 (opens new window)
  • 加密解密 (opens new window)
  • bilibili字幕提取 (opens new window)
  • 道理 (opens new window)
  • 关于博主

    • Github (opens new window)
    • CSDN (opens new window)
  • 关于本站

    • 如何搭建博客网站 (opens new window)
  • 简历项目
  • Java面试题

    • Java面试题
    • JUC面试题

      • JUC基础知识
        • 创建线程的四种方式
        • 线程的 run()和 start()有什么区别?
        • 线程包括哪些状态,状态之间是如何变化的/Java线程的生命周期
        • java 中 wait 和 sleep 方法的不同?
        • 什么是CompletableFuture
      • 线程并发安全
      • 线程池
      • JUC面试题
    • JVM面试题

    • Java集合面试题
  • 后端开发
  • 数据库面试题
  • Maven
  • MyBatis面试题
  • Spring面试题
  • SpringBoot面试题
  • SpringCloud面试题

  • Redis面试题

  • 设计模式
  • 中间件

  • 场景题
  • Other

  • Random

  • 项目面试题

  • 面试
  • Java面试题
  • JUC面试题
2025-04-11
0
0
目录

JUC基础知识

、

# 线程和进程的区别?

  • 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务
  • 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)

# 并行和并发有什么区别?

现在都是多核CPU,在多核CPU下 并发是同一时间应对多件事情的能力,多个线程轮流使用一个或多个CPU 并行是同一时间动手做多件事情的能力,4核CPU同时执行4个线程

# 创建线程的四种方式

在java中一共有四种常见的创建方式,分别是:继承Thread类、实现runnable接口、实现Callable接口、线程池创建线程。通常情况下,我们项目中都会采用线程池的方式创建线程。

# Thread和Runnable的区别

Thread和Runnable的区别主要体现在以下三个方面:

  • Thread是一个类,而Runnable是一个接口。

  • 由于Java的单继承限制,如果某个类已经继承了其他父类,要实现多线程就只能通过实现Runnable接口的方式。

  • Runnable是线程任务的顶级接口,而Thread类其实是实现了Runnable接口。
  • 无论是继承Thread还是实现Runnable,都需要重写run()方法定义线程执行逻辑。
  • Runnable代表一个可执行的任务,Thread是实际执行任务的线程。
  • 这种设计实现了任务与线程的松耦合:我们可以定义Runnable任务,然后交给Thread或线程池执行,提高代码的灵活性。

# runnable 和 callable 有什么区别

  1. Runnable 接口run方法没有返回值;Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
  2. Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
  3. Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛

# 线程的 run()和 start()有什么区别?

start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。

run(): 封装了要被线程执行的代码,可以被调用多次。

# 线程两次调用start方法

java里的线程只能调用一次start方法,第二次调用会抛出IllegalThreadStateException 在java里面的线程生命周期包括六种状态,

  1. NEW表示线程已创建,但尚未调用 start(),
  2. RUNNABLE,在这个状态下呢,线程可能是正在运行,也可能是在等待CPU资源的分配,
  3. BROCKED,表示线程处于锁等待状态,
  4. WAITING,表示线程处于条件等待状态,当触发条件后会唤醒,比如wait对应的notify方法,
  5. TIMED_WAITING,它和WAITING的状态是相同的,只是多了一个超时条件触发机制,
  6. TERMINATED,表示线程的执行结束

当我们第一次调用start方法后,线程的状态可能会处于终止状态,或者非new状态,再调用一次start方法,相当于让这个正在运行的线程重新运行一遍,不管是从线程的安全性角度来看,还是说从线程本身的执行逻辑来看,它都是不合理的,因此啊为了避免这样的问题,在线程运行的时候,会先去判断当前线程的状态是否为NEW

# 线程包括哪些状态,状态之间是如何变化的/Java线程的生命周期

在JDK中的Thread类中的枚举State里面定义了6中线程的状态分别是:新建、可运行、终结、阻塞、等待和有限等待六种。

关于线程的状态切换情况比较多。我分别介绍一下

当一个线程对象被创建,但还未调用 start 方法时处于新建NEW状态,调用了 start 方法,就会由新建进入可运行RUNNABLE状态。如果线程内代码已经执行完毕,由可运行进入终结TERMINATED状态。当然这些是一个线程正常执行情况。

如果线程获取锁失败后,由可运行进入 Monitor 的阻塞队列阻塞BLOCKED,只有当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行状态

如果线程获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从可运行状态释放锁等待WAITING状态,当其它持锁线程调用 notify() 或 notifyAll() 方法,会恢复为可运行状态

还有一种情况是调用 sleep(long) 方法也会从可运行状态进入有时限等待TIMED_WAITING状态,不需要主动唤醒,超时时间到自然恢复为可运行状态

线程可以通过三种方法进入WAITING状态:

  • Object.wait():等待其他线程调用notify()或notifyAll()。
  • Thread.join():等待目标线程执行完毕。
  • LockSupport.park():暂停当前线程。

线程进入等待状态,但会在指定的时间后自动唤醒。线程可以通过四种带有超时时间的方法进入TIMED_WAITING状态:

  • Thread.sleep(long millis):睡眠指定时间。
  • Object.wait(long timeout):等待指定时间。
  • Thread.join(long millis):等待目标线程指定时间。
  • LockSupport.parkNanos(long nanos):暂停当前线程指定时间。

# 线程的状态BLOCKED和WAITING有什么区别

BLOCKED和WAITING都是属于线程的阻塞等待状态, BLOCKED状态是线程在竞争synchronized同步锁失败时进入的被动阻塞状态。当多个线程尝试获取同一个synchronized锁时,未竞争到锁的线程会被JVM自动置为BLOCKED状态。这种状态仅出现在同步锁竞争场景,且线程的唤醒完全依赖锁持有者释放锁(无需外部干预)。

WAITING状态是线程主动进入的等待状态,通过显式调用以下方法触发:

  • Object.wait()
  • Thread.join()
  • LockSupport.park()

进入WAITING状态的线程必须依赖特定方法唤醒(如Object.notify()唤醒wait()的线程,LockSupport.unpark()唤醒park()的线程),否则会持续等待。

BLOCKED和waiting这两个状态的最大区别有两个,

  1. 触发方式:BLOCKED是被动由JVM触发(锁竞争失败),WAITING是主动通过代码调用触发。
  2. 唤醒机制:BLOCKED线程的唤醒是自动的(锁释放后由JVM调度),WAITING线程必须依赖显式唤醒。

# 新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?⭐⭐⭐

可以这么做,在多线程中有多种方法让线程按特定顺序执行,可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。

比如说:

使用join方法,T3调用T2,T2调用T1,这样就能确保T1就会先完成而T3最后完成

# notify()和 notifyAll()有什么区别?

notifyAll:唤醒所有wait的线程

notify:只随机唤醒一个 wait 线程

# java 中 wait 和 sleep 方法的不同?

共同点

  • wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态

不同点

  • 方法归属不同
    • sleep(long) 是 Thread 的静态方法
    • 而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有
  • 醒来时机不同
    • 执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来
    • wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去
    • 它们都可以被打断唤醒
  • 锁特性不同(重点)
    • wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制
    • wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)
    • 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)

# wait和sleep是否会触发锁的释放以及CPU资源的释放

object.wait()方法它会释放锁资源以及CPU资源,而thread.sleep方法不会释放锁资源,但是会释放CPU资源 wait() 必须在 synchronized 同步代码块或方法中使用,因为它依赖于对象的监视器锁(Monitor Lock)。 当线程调用wait()方法时,表示当前线程的工作已经处理完成了,意味着其他竞争同一共享资源的线程有机会去执行,但其他线程需要竞争锁资源,所以wait的方法必须要释放锁,否则就会导致死锁的一个问题, thread.sleep()方法,只是让线程进入睡眠状态,而这个方法并没有强制要求加synchronized同步锁,而从它的功能和语义来说也没有这个必要,当然如果在一个synchronized同步代码块里面去调用,Thread.sleep()方法,也并不会触发锁的一个释放,

# wait、notify必须在synchronized代码块中使用)

wait()和notify()方法表示线程的阻塞与唤醒,唤醒需要线程之间的通信,而通信除了通过过管道流的方法还有通过共享变量的改变来进行,wait和notify的底层就是通过操作对象的监视器(monitor)来进行通信,而synchronized提供了获取和释放监视器的机制。

为了防止wait和notify的错误使用,Jdk强制要求wait和notify写在synchronized中, 否则会抛出IllegalMonitorStateException

wait()通常要在循环中调用,以应对虚假唤醒(spurious wakeup)

# 如何停止一个正在运行的线程?

  • 使用stop方法强行终止(不推荐,方法已作废)
  • 使用interrupt方法中断线程(修改中断状态,需要使用interrupt判断中断状态主动结束线程,如果线程是等待状态则会抛出InterruptedException )

Java线程本质是操作系统线程的封装,线程调度由操作系统决定。java中的start()方法只是通知OS线程可被调度,但具体执行由OS的CPU调度算法控制。所以理论上来说,要在java里面去中断一个正在运行的线程,只能像类似于LINUX里使用kill命令去结束进程的方式,java 里提供了一个stop方法,可以去强制终止,但是这种方式是不安全的,因为有可能这个线程的任务还没有执行完成,导致出现运行结果不正确的现象,

要想安全的去中断一个正在运行的线程,可以使用java 提供的interrupt的方法,那么这个方法需要配合isInterrupted方法使用,这种方法并不是强制中断,而是告诉正在运行的线程你可以停止,不过是否要中断,取决于正在运行的线程,所以它能够保证线程运行结果的一个安全性,若线程处于阻塞状态(如sleep()/wait()),会抛出InterruptedException。

# 什么是CompletableFuture

CompletableFuture 是 Java 8 引入的一个并发工具类,它提供了一种强大且灵活的方式来处理异步任务及其执行结果。在 CompletableFuture 出现之前,Java 主要依赖 Callable 和 Future 机制来获取异步线程的执行结果。但Future 是通过阻塞等待的方式来实现的,对性能不是很友好

相比之下,CompletableFuture 允许我们将耗时任务提交给线程池异步执行,同时继续处理其他任务。当异步任务完成后,CompletableFuture 会自动触发回调方法,我们可以在回调中处理异步任务的任务结果,从而避免了阻塞等待的问题。这种方式类似于响应式编程,能够更高效地利用系统资源。

此外,CompletableFuture 提供了丰富的组合方法(如 thenApply、thenAccept、thenRun 等),支持链式调用,使得异步任务的编排更加灵活和直观。这使得开发者能够以更简洁的方式编写复杂的异步逻辑,提升代码的可读性和可维护性。

CompletableFuture 是 JDK 1.8 引入的基于事件驱动的异步回调工具类,核心功能是在异步任务完成后触发后续动作,从而解决传统同步调用效率低的问题。

比如说在一个支付的业务逻辑里面,涉及到查询订单,支付和发送邮件通知这三个逻辑,那么这三个逻辑是按照顺序同步去实现的,调用线程必须完全阻塞,直到所有步骤串行执行完毕。而使用CompletableFuture ,调用线程仅需要注册回调(如thenAccept),而不用阻塞等待异步任务执行完毕才能执行下一步操作

CompletableFuture 的提供了五种任务组合方式,可以把多个异步任务,组成一个具有先后关系的处理链,然后基于事件来驱动任务链的执行, 第一种是thenCombine,并行执行两个独立任务,双任务均完成后触发回调,可合并两者结果, 第二种是thenCompose,串行执行,形成任务链, 任务A完成后触发任务B,B依赖A的结果 第三种是thenAccept,接收前序任务结果,不返回任何新的计算值, 第四种是thenApply,和thenAccept一样,接收前序任务结果,但它是具有返回值的方法, 第五种是thenRun,纯回调触发,不接收前序任务结果,就是说第一个任务执行完成以后,只触发执行一个实现了RUNNABLE接口的一个任务, 所以CompletableFuture弥补原本Future的不足,使得程序可以在非阻塞的状态下去完成异步回调机制

方法 输入 输出 是否接收前序结果 是否产生新结果 典型用途
thenCombine 两个Future 任意类型 是(两者) 是 合并并行任务结果
thenCompose 一个Future Future 是 是(Future) 异步任务链
thenAccept 一个Future void 是 否 最终结果消费
thenApply 一个Future 任意类型 是 是 数据转换
thenRun 一个Future void 否 否 完成后触发副作用操作
上次更新: 2025/5/19 20:55:20
Java面试题
线程并发安全

← Java面试题 线程并发安全→

最近更新
01
项目优化
05-06
02
项目优化
05-06
03
延迟消息
05-05
更多文章>
Theme by Vdoing | Copyright © 2025-2025 Cyan Blog
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式