Java 多线程和并发面试题16 Mar 2025 | 26 分钟阅读 多线程和同步是 Java 编程中最 the 重要的两个概念。大多数软件公司通常会问多线程和并发相关的问题来测试面试者的知识。在本节中,我们将介绍一些在面试中经常被问到的重要问题。 多线程面试题1) 什么是多线程?多线程是一个同时执行多个线程的过程。多线程用于实现多任务处理。它消耗的内存更少,并提供快速高效的性能。它的主要优点是:
2) 什么是线程?线程是轻量级的子进程。它是一条独立的执行路径,因为每个线程都在不同的堆栈帧中运行。一个进程可以包含多个线程。线程共享进程资源,但它们仍然独立执行。 更多详情。3) 区分进程和线程?进程和线程之间存在以下区别:
![]() 4) 您对线程间通信有什么理解?
5) wait() 方法在 Java 中的目的是什么?wait() 方法由 Object 类在 Java 中提供。它用于线程间通信。java.lang.Object.wait() 方法用于暂停当前线程,并等待直到另一个线程调用 notify() 或 notifyAll() 方法。其语法如下。 6) 为什么 wait() 方法必须从同步块中调用?我们必须调用 wait() 方法,否则它将抛出 java.lang.IllegalMonitorStateException 异常。此外,我们需要 wait() 方法与 notify() 和 notifyAll() 方法进行线程间通信。因此,为了实现正确和高效的通信,它必须存在于同步块中。 7) 多线程有哪些优点?多线程编程具有以下优点:
8) 解释线程的生命周期?线程在执行期间可以具有以下任一状态: 新建:在此状态下,使用 new 操作符创建了一个 Thread 类对象,但线程尚未启动。直到我们调用 start() 方法,线程才会启动。 可运行:在此状态下,调用 start() 方法后,线程已准备好运行。但是,线程尚未被线程调度器选中。 运行:在此状态下,线程调度器从就绪状态选择线程并开始运行。 等待/阻塞:在此状态下,线程未运行但仍然存在,或者它正在等待另一个线程完成。 终止/死亡:如果线程满足以下任一条件,则处于终止或死亡状态:
![]() 9) 抢占式调度和时间片轮转有什么区别?在抢占式调度中,最高优先级的任务会一直执行,直到它进入等待或终止状态,或者出现一个更高优先级的任务。在时间片轮转中,一个任务会执行预定的时间片,然后重新进入就绪任务池。调度器然后根据优先级和其他因素确定应该执行哪个任务。 10) 什么是上下文切换?在上下文切换中,会存储进程(或线程)的状态,以便稍后可以恢复它并从同一点恢复执行。上下文切换允许多个进程共享同一个 CPU。 11) 区分用于创建线程的 Thread 类和 Runnable 接口?可以使用两种方式创建线程。
但是,这两种方式的主要区别如下:
12) join() 方法有什么作用?join() 方法等待线程终止。换句话说,它会使当前正在执行的线程停止执行,直到它加入的线程完成其任务。join() 方法在 Thread 类中以以下方式重载。
13) 描述 sleep() 方法的用途和工作原理。Java 中的 sleep() 方法用于将线程阻塞特定时间,这意味着它会暂停线程的执行特定时间。有两种方法可以实现这一点: 语法
sleep() 方法的工作原理 当我们调用 sleep() 方法时,它会暂停当前线程的执行给定的时间,并将优先级赋予另一个线程(如果可用)。此外,当等待时间结束后,之前的线程会将其状态从等待变为可运行,并进入运行状态,然后整个过程会一直持续下去,直到执行完成。 14) wait() 和 sleep() 方法有什么区别?
15) 启动线程两次是否可能?不行,我们不能重新启动线程,一旦线程启动并执行完毕,它就会进入终止状态。因此,如果我们尝试启动一个线程两次,它将抛出运行时异常 "java.lang.IllegalThreadStateException"。请看以下示例。 输出 thread is executing now........ Exception in thread "main" java.lang.IllegalThreadStateException at java.lang.Thread.start(Thread.java:708) at Multithread1.main(Multithread1.java:13) 16) 可以直接调用 run() 方法而不是 start() 方法吗?是的,直接调用 run() 方法是有效的,但它不会作为线程工作,而是作为普通对象工作。线程之间不会发生上下文切换。当我们调用 start() 方法时,它会内部调用 run() 方法,该方法为线程创建一个新的堆栈;而直接调用 run() 不会创建新的堆栈。 更多详情。17) 守护线程呢?守护线程是低优先级的线程,为用户线程提供后台支持和服务。如果程序只剩下守护线程,而所有其他用户线程都已结束/死亡,则 JVM 会自动终止守护线程。Thread 类提供了两个守护线程的方法:
18) 如果用户线程已启动,我们能将其设为守护线程吗?不行,如果我们这样做,它将抛出 IllegalThreadStateException。因此,我们只能在启动线程之前创建守护线程。 输出 Running thread is daemon... Exception in thread "main" java.lang.IllegalThreadStateException at java.lang.Thread.setDaemon(Thread.java:1359) at Testdaemon1.main(Testdaemon1.java:8) 19) 什么是关机钩子?关机钩子是在 JVM 关闭之前隐式调用的线程。因此,我们可以在 JVM 正常或异常关闭时使用它来执行资源清理或保存状态。我们可以通过使用以下方法添加关机钩子: 关于关机钩子的一些要点:
20) 何时应中断线程?当我们想打破线程的 sleep 或 wait 状态时,应中断线程。我们可以通过调用 interrupt() 并抛出 InterruptedException 来中断线程。 更多详情。21) 什么是同步?同步是控制多个线程对任何共享资源的访问的能力。它用于:
当多个线程尝试执行同一任务时,可能会出现错误的结果,因此为了解决此问题,Java 使用同步过程,该过程一次只允许一个线程执行。同步可以通过三种方式实现:
同步块的语法 更多详情。22) Synchronized 块的目的是什么?Synchronized 块可用于对方法的任何特定资源执行同步。一次只有一个线程可以执行特定资源,所有其他尝试进入同步块的线程都将被阻塞。
23) Java 对象可以被某个特定线程锁定以进行独占使用吗?是的,我们可以通过将对象放入 "synchronized" 块来锁定它。被锁定的对象对除显式声明它的线程以外的任何线程都不可访问。 24) 什么是静态同步?如果我们声明任何静态方法为 synchronized,那么锁将作用于类而不是对象。如果我们使用 synchronized 关键字放在方法前面,它将锁定对象(一次只能一个线程访问一个对象),但如果我们使用 static synchronized,它将锁定类(一次只能一个线程访问一个类)。 更多详情。
25) notify() 和 notifyAll() 方法有什么区别?notify() 方法用于唤醒一个等待线程,而 notifyAll() 方法用于唤醒所有处于等待状态的线程。 26) 编程中的死锁情况是什么?死锁是指每个线程都在等待一个被另一个等待线程持有的资源。在这种情况下,没有一个线程能执行,也没有机会得到执行。相反,所有线程之间存在普遍的等待状态。死锁是一个非常复杂的情况,可能会在运行时破坏我们的代码。 更多详情。27) 如何检测死锁情况?如何避免?我们可以通过在 CMD 上运行代码并收集线程转储来检测死锁情况,如果代码中存在任何死锁,则提示符(CMD)上会出现一条消息。 有以下方法可以避免 Java 中的死锁情况:
28) Java 中的线程调度器是什么?在 Java 中,当我们创建线程时,它们会由线程调度器进行管理,线程调度器是 JVM 的一部分。线程调度器仅负责决定哪个线程应该被执行。线程调度器使用两种机制来调度线程:抢占式和时间片轮转。 Java 线程调度器还负责决定线程的以下事项:
29) 在多线程编程中,每个线程都有自己的堆栈吗?是的,在多线程编程中,每个线程都在内存中维护自己的或独立的堆栈区域,因此每个线程都是相互独立的。 30) 如何实现线程安全?当一个方法或类对象被多个线程同时使用而没有任何竞态条件时,该类就是线程安全的。线程安全用于使程序在多线程编程中安全使用。可以通过以下方式实现:
31) 什么是竞态条件?竞态条件是在多线程编程中,当多个线程试图同时访问共享资源时发生的问题。正确使用同步可以避免竞态条件。 32) Java 中的 volatile 关键字是什么?Volatile 关键字用于多线程编程以实现线程安全,因为对一个 volatile 变量的更改对所有其他线程都是可见的,因此一个变量可以一次由一个线程使用。 33) 您对线程池有什么理解?
线程池的优点是:
34) 用户级线程和内核级线程有什么区别?用户级线程由应用程序管理,操作系统不知道它们的存在。另一方面,内核级线程由操作系统内核管理,为多任务处理和并行执行提供更好的支持。 35) 解释多线程中的竞态条件概念以及如何缓解它们。当程序的结果取决于多个线程访问共享资源的计时或交错时,就会发生竞态条件。可以使用锁、原子变量等同步机制或使用不可变数据结构来缓解它们。 36) volatile 关键字在 Java 多线程中的意义是什么?volatile 关键字确保变量的值始终从主内存读取并写入主内存,而不是从线程的缓存中读取。它保证了一个线程所做的更改对所有其他线程可见,从而防止了与缓存相关的不一致。 37) 同步对多线程程序的性能有什么影响?同步会因获取和释放锁的开销而影响性能。过度的同步可能导致争用和并行性下降。平衡同步以确保线程安全而不牺牲性能非常重要。 38) Atomic 类的目的是什么?Atomic 类提供对变量的原子操作,而无需显式同步。它们用于执行复合操作,例如递增计数器或以线程安全的方式更新共享变量。 39) 解释线程局部变量的概念及其用法。线程局部变量是每个线程都有一个独立副本的变量。它们通常用于维护线程特定的数据,而无需同步。Java 中的 ThreadLocal 类提供了创建线程局部变量的支持。 40) 可以不使用同步块或方法来实现互斥吗?是的,可以使用 java.util.concurrent 包中的其他同步机制,如 ReentrantLock 或 Semaphore 来实现互斥。与同步块相比,这些类提供了更多的灵活性和功能。 41) 讨论在多线程编程中使用锁的优点和局限性。锁提供了对同步的显式控制,并且可以处理比内部锁(同步块)更复杂的场景。但是,它们需要仔细管理以避免死锁等问题,并且与同步块相比,它们可能会引入额外的开销。 42) Java 内存模型是什么?它与多线程有什么关系?Java 内存模型定义了线程如何通过内存进行交互。它确保一个线程对共享变量所做的更改对于其他线程是可见的,具体遵循特定规则。理解 Java 内存模型对于编写正确高效的多线程程序至关重要。 43) Java 如何处理线程优先级,以及它们有何影响?Java 提供了线程优先级来影响线程调度器对线程的调度顺序。高优先级线程会优先获得调度,但基于优先级的线程调度行为可能因平台和 JVM 实现而异。 44) 什么是守护线程?它与用户线程有什么区别?守护线程是低优先级的线程,在后台运行,为用户线程提供服务。与用户线程不同,当所有用户线程执行完毕后,守护线程不会阻止 JVM 退出。 45) 讨论使用 wait() 和 notify() 方法进行线程间通信的优点和缺点。优点: wait() 和 notify() 方法提供了一种简单的线程间通信机制,允许线程有效地等待特定条件。它可用于实现生产者-消费者模式和其他同步场景。 缺点:它们需要使用同步块进行正确同步,不正确的使用可能导致错过信号或死锁等问题。此外,notify() 只唤醒一个等待的线程,这在某些情况下可能不是期望的结果。 46) 解释线程安全的概念及其在并发编程中的重要性。线程安全确保共享资源以避免竞态条件和维护数据一致性的方式进行访问。在并发编程中,它对于防止不可预测的行为和确保多线程程序的正确性至关重要。 47) 调试多线程应用程序面临哪些挑战?多线程应用程序会引入非确定性行为,使得重现和调试问题变得困难。挑战包括竞态条件、死锁和线程交错,这些都需要仔细分析和调试技术,例如线程转储和同步调试工具。 48) 解释乐观锁和悲观锁的区别?乐观锁假定线程之间的冲突很少,并允许多个线程并发访问共享资源。另一方面,悲观锁假定冲突很常见,并限制对共享资源的访问,通常使用锁来强制执行互斥。 49) Java 如何支持多核处理器上的并行和并发?Java 通过 Executor 框架、ForkJoinPool 和 java.util.concurrent 包等功能来支持并行和并发,使开发人员能够利用多核来实现高效的并发任务执行。 50) Executor 框架在 Java 中的目的是什么?它如何简化多线程编程?Executor 框架提供了更高级别的抽象来管理线程执行和调度任务。它将任务提交与执行解耦,简化了线程管理,并允许轻松配置线程池和任务执行策略。 并发面试题51) 并发 API 的主要组成部分是什么?并发 API 可以使用 java.util.Concurrent 包的类和接口进行开发。java.util.Concurrent 包中有以下类和接口:
52) Java 并发 API 中的 Executor 接口是什么?Executor 接口属于 java.util.concurrent 包。它是一个用于执行新任务的接口。Executor 接口的 execute() 方法用于执行指定的命令。execute() 方法的语法如下。 请看以下示例 输出 Running Thread! Thread Completed 53) 什么是 BlockingQueue?java.util.concurrent.BlockingQueue 是 Queue 接口的子接口。它支持在插入新值之前等待空间可用性,或在从队列检索元素之前等待队列非空等操作。请看以下示例。 输出 Added: 96 Removed: 96 Added: 8 Removed: 8 Added: 5 Removed: 5 54) 如何使用 BlockingQueue 实现生产者-消费者问题?生产者-消费者问题可以通过以下方式使用 BlockingQueue 来解决。 输出 Produced: 0 Produced: 1 Produced: 2 Produced: 3 Produced: 4 Produced: 5 Produced: 6 Produced: 7 Produced: 8 Produced: 9 Consumed: 0 Consumed: 1 Consumed: 2 Consumed: 3 Consumed: 4 Consumed: 5 Consumed: 6 Consumed: 7 Consumed: 8 Consumed: 9 55) Java Callable 接口和 Runnable 接口有什么区别?Callable 和 Runnable 接口都用于希望在多个线程中执行的类。但是,两者之间存在以下主要区别:
56) Java 并发中的原子操作是什么?
57) Java 并发 API 中的 Lock 接口是什么?java.util.concurrent.locks.Lock 接口用作同步机制。它的工作方式类似于同步块。锁和同步块之间存在一些差异,如下所示。
58) 解释 ExecutorService 接口。ExecutorService 接口是 Executor 接口的子接口,并添加了管理生命周期的功能。请看以下示例。 输出 Shutdown executor shutdown finished 59) 同步编程和异步编程在线程方面有什么区别?同步编程:在同步编程模型中,会分配一个线程来完成任务,然后线程开始处理它,直到完成分配的任务,它才能用于其他任务。 异步编程:在异步编程中,一个作业可以由多个线程完成,因此它提供了对各种线程的最大利用率。 60) 您对 Java 中的 Callable 和 Future 有什么理解?Java Callable 接口:在 Java 5 中,Callable 接口定义在 java.util.concurrent 包中。它类似于 Runnable 接口。但它可以返回结果,并且可以抛出异常。它还提供 run() 方法用于执行线程。Java Callable 可以返回任何对象,因为它使用了泛型。 语法 Java Future 接口:Java Future 接口提供并发进程的结果。Callable 接口返回 java.util.concurrent.Future 的对象。 Java Future 接口提供了以下方法用于实现。
61) ScheduledExecutorService 接口和 ExecutorService 接口有什么区别?ExecutorServcie 和 ScheduledExecutorService 都是 java.util.Concurrent 包的接口,但 scheduledExecutorService 提供了额外的方法,可以延迟或以固定的时间间隔执行 Runnable 和 Callable 任务。 62) 定义 Java 中的 FutureTask 类?Java FutureTask 类提供了 Future 接口的基本实现。只有在完成一个任务的执行后才能获得结果,如果计算未完成,get 方法将阻塞。如果执行已完成,则无法重新启动,也无法取消。 语法 63) 什么是信号量?信号量是一种同步原语,它限制可以并发访问共享资源的线程数量。它维护一组许可证来控制对资源的访问,允许固定数量的线程获取许可证并执行访问共享资源的 कोड。 64) 解释 CountDownLatch 的概念。CountDownLatch 是一种同步辅助工具,它允许一个或多个线程等待直到其他线程中执行的一组操作完成。它初始化时带有一个计数,每个线程在完成其操作时都会递减计数。当计数达到零时,等待锁存器的线程将被释放。 65) 什么是 CyclicBarrier?如何使用它?CyclicBarrier 是一种同步辅助工具,它允许一组线程在预定义的屏障处等待,直到所有线程都到达屏障后再继续。所有线程都到达屏障后,它们将同时释放。CyclicBarrier 在所有线程越过屏障后可以重置为其初始状态。 66) 讨论 Phaser 类的目的。Phaser 是一种同步屏障,它允许多个线程在预定义的阶段同步它们的执行。它比 CountDownLatch 和 CyclicBarrier 提供了更灵活的同步,允许线程在不同阶段动态注册和注销。Phaser 可用于实现多阶段算法。 67) Exchanger 类的目的是什么?Exchanger 类提供了一个同步点,两个线程可以在该点交换对象。每个线程调用 exchange() 方法,提供要交换的对象,并等待直到另一个线程也调用 exchange()。一旦两个线程都调用了 exchange(),它们就会交换它们的对象并继续执行。 68) 解释线程封闭的概念。线程封闭是一种并发控制技术,其中某个数据一次只能由单个线程访问。它通过防止多个线程并发访问来确保数据的一致性并避免竞态条件。线程封闭可以通过将数据封装在线程内或使用同步机制来实现。 69) ReadWriteLock 接口的目的是什么?ReadWriteLock 接口提供了一种在多线程环境中控制对共享资源的访问的机制。与传统的锁不同,ReadWriteLock 允许多个线程并发读取资源,同时为写入提供独占访问。在读比写更频繁的场景中,它可以提高性能。 70) 讨论线程安全集合的概念。线程安全集合是由 java.util.concurrent 包提供的。这些数据结构可以被多个线程安全地并发访问和修改。这些集合,如 ConcurrentHashMap 和 CopyOnWriteArrayList,内部处理同步,以确保线程安全,而无需用户进行外部同步。 71) ForkJoinPool 类的目的是什么?ForkJoinPool 是一种特殊的 ExecutorService 实现,专用于并行化递归分治算法。它管理一个工作线程池,这些线程使用工作窃取算法执行任务,其中空闲线程从其他线程的队列中窃取任务,以最大化 CPU 利用率并最小化争用。 72) 解释并行流的概念。并行流是 Java 8 中引入的一项功能,用于利用多核处理器并行执行流操作。它们可以自动并行化集合上的 map、filter 和 reduce 等操作,将工作负载分布到多个线程上,以在多核系统上提高性能。 73) CompletableFuture 类的目的是什么?CompletableFuture 是 Java 8 中引入的一个类,用于表示异步计算的未来结果。它提供了一个强大的 API 来组合异步操作、处理异常和链接依赖的计算。CompletableFuture 支持同步和异步执行,使其在构建复杂的异步工作流方面具有通用性。 74) 讨论非阻塞算法的概念。非阻塞算法是并发控制技术,它们在存在争用或线程故障的情况下也能确保进度,而无需锁或阻塞同步原语。它们使用原子操作和比较并交换 (CAS) 指令来操作共享数据,而不会阻塞线程,从而带来更好的可伸缩性和响应能力。 75) Phaser 类的目的是什么?Phaser 是 Java 7 中引入的一种同步屏障,它允许线程在多个阶段进行同步。它类似于 CountDownLatch 和 CyclicBarrier,但通过允许在不同阶段动态注册和注销参与者(线程)提供了更大的灵活性。Phaser 支持一次性同步和循环同步场景。 76) 讨论 ThreadLocal 类的概念。ThreadLocal 是 Java 中提供的用于创建线程局部变量的类,每个线程都有自己的独立变量副本。ThreadLocal 变量通常用于存储线程特定的数据,而无需同步,从而提高多线程应用程序的性能并减少争用。 77) LockSupport 类的目的是什么?LockSupport 是 Java 中提供的用于低级线程同步的实用程序类。它允许线程根据可用性阻塞和解除阻塞,类似于 wait() 和 notify() 方法,但具有更大的灵活性和控制力。LockSupport 可用于实现自定义同步原语和线程之间的协调机制。 78) 如何显式定义线程的堆栈大小?Thread 类提供了一个构造函数,通过该构造函数我们可以显式确定新线程的堆栈大小。 堆栈大小参数的效果(如果有)高度依赖于平台。在某些平台上,较高的堆栈大小值可能允许更深的递归深度,然后才抛出 StackOverflowError,而在其他平台上,它可能没有任何影响。 79) 可以覆盖 Thread.start() 方法吗?是的,我们可以覆盖 Thread.start() 方法,但请注意,覆盖的方法将像主线程仅执行的普通方法调用一样执行。它不会像默认的 start() 方法那样启动一个新线程。请看以下示例。 上面的代码片段创建了一个新的 ChildThread 实例并调用其 start() 方法。它不会启动一个新线程,run() 方法也不会被执行。 输出 Overriding start() method in ChildThread 要初始化新线程,我们必须从子线程调用 super.start() 方法;只有这样,run() 方法才会被执行。 输出 Overriding start() method in ChildThread ChildThread run() method 80) 什么是线程优先级?默认线程优先级是多少?在 Java 中,JVM 为每个线程分配一个 1 到 10 之间的数字,称为线程优先级。优先级在线程执行期间由线程调度器使用。在为线程分配处理器时,线程调度器会优先考虑优先级最高的线程。 请注意,任何线程的默认优先级都从父线程继承。主线程的默认优先级是 5,因此所有子线程的默认优先级也是 5。 Thread 类定义了三个常量来表示任何线程的最小、最大和默认优先级。 Thread.MIN_PRIORITY = 1; Thread.NORM_PRIORITY = 5; Thread.MAX_PRIORITY = 10; 81) 什么是守护线程?守护线程是低优先级线程。它在后台执行,为非守护线程(执行项目主逻辑的线程称为非守护线程或用户线程)提供支持。Java 中的守护线程也称为服务提供者线程。 守护线程的属性
82) 守护线程和用户线程有什么区别?
83) 可以将同步方法声明为抽象方法吗?如果不能,为什么?一般而言,同步需要在执行方法之前获取锁。我们知道锁是实现特性。由于抽象方法没有实现(也没有锁),因此无法对其进行同步。 因此,将同步方法声明为抽象方法是没有意义的。 84) submit() 和 execute() 方法有什么区别?ExecutorService 接口提供了 submit() 和 execute() 两个方法来向线程池提交新任务。
85) 如何在 Java 8 并发中创建和使用流?在 Java 8 中,Stream API 提供了一种以并行方式处理大量数据集合的方法。我们可以使用集合的 stream() 方法或 Stream.of() 方法来创建流。我们可以使用各种中间操作,如 filter() 或 map(),在使用 forEach() 或 collect() 等终端操作获取最终结果之前来操作流中的数据。 下一个主题Java 集合面试题 |
我们请求您订阅我们的新闻通讯以获取最新更新。