Skip to content

右侧栏

JAVA 多线程面试题

[toc]

什么是原子性?

原子性指的是一个或者多个操作,要么全部执行,要么都不执行,再执行的过程中,不被其他操作中断或干扰。

在并发过程中,原子性是一个非常重要的概念,如果操作不是原子的,在多线程并发环境下,多个线程可能访问同一个变量,可能会导致数据不一致。

Java 中基本数据类型的赋值和读取是原子的吗?

处理long 和 double (可以通过 volatile 修饰实现原子性)注意:volatile 修饰的 i++,i--操作不具有原子性,volatile 主要是为了解决可见性,其他的基本数据类型的读取和赋值都是原子性的。

java
int x = 100;//原子
x++;//非原子
int y = x;//非原子
x = x + 1;//非原子

上面的代码只有第一行是原子的,其他三个都包含两个以上的操作,首先都是要求读取 x 变量的值,经过计算后再将新的值写入到内存中去

volatile 的局限性

  • 不保证原子性
    • volatile 变量的复合操作(如 i++)仍是非原子的,可能被线程切换打断。
  • 不解决指令重排序
    • 编译器或 CPU 仍可能重排 volatile 与非 volatile 操作的顺序(除非配合内存屏障)。
  • 不适用于多线程同步
    • 现代多核 CPU 的缓存一致性协议(如 MESI)通常能自动同步内存,volatile 的可见性作用被弱化,而原子性和顺序性成为更关键的问题。

什么是可见性?如何保证可见性?

可见性是指一个线程对共享变量的修改,其他线程可以立刻看到该值。

多个线程可能会访问同一个变量,由于线程本地缓存(如 cpu 缓存)的存在,一个线程对变量的修改,但没有及时刷新到到主内存,其他线程读取的可能是旧的副本值,而不是最新的。这就导致了可见性问题。

如何保证可见性?

  1. volatile 关键字。

    1. volatile 保证的了对变量的写操作会立刻写入到主内存中去。
    2. 对变量的读操作会从主内存中读取最新值。
    3. 保证可见性,但是不保证原子性。
  2. 使用 Synchronized 或 lock

    Synchronized 关键字不仅保证互斥性(即同一个时刻只有一个线程执行临界区代码)还保证可见性:进入 Synchronized 代码块时,会清空本地缓存,从主内存中读取最新值;退出时,会将变量刷新到主内存中去。

  3. 使用 final(初始化时的可见性)

    使用 final 字段,java 在构造函数完成后,会确保所有线程都能够看到该字段的正确值。

什么是有序性?如何保证有序性?

有序性指代码运行的顺序符合代码编写时的先后顺序。

但是在实际执行中,出于优化的目的(如提高性能),编译器、CPU、和 java 虚拟机都可能对代码进行“指令重排序”,从而打破原本代码书写的顺序。这就可能导致并发程序中产生预期之外的结果。

如何保证有序性?

  1. volatile 关键字

    1. 限制了某些类型的指令重排序

    2. 写入 volatile 变量之前的所有操作,在内存语义上都会在写操作之前完成

    3. 保证了 写-读操作的有序性。

      java
      volatile boolean ready = false;
      int a = 0
      //线程 A修改值
      a=42;
      
      read = true; //volatile 写屏障,强制前面的先执行
      //线程 B
      if (ready) {
      		System.out.println(a);//保证看到 a=42
      }
  2. Synchronized / lock

    1. Synchronized 不仅保证互斥性和可见性,还会禁止同步块内的重排序。
    2. 进入同步块:读取主内存,禁止重排序
    3. 退出同步块;刷行变量到主内存,保证前后代码的顺序。
  3. 内存屏障(Memory Barrier)或 happens-before 规则(JMM 内存模型)

    1. Java 内存模型通过 happens-before 关系来定义哪些操作之间是有序的。
    2. 比如:
      1. Synchronized 之间的操作是有序的
      2. volatile 写->读是有序的
      3. 线程 start() 之前的操作,对新线程是可见且有序的
      4. 线程 join()之后的操作,一定在被 join 的线程所有操作之后

为什么要使用多线程?

  1. 更好的利用多核 cpu(并行计算)

  2. 提升程序的响应速度(改善用户体验)

  3. 提高程序执行效率(cpu利用率),防止阻塞

    单线程程序在 io 时会阻塞,cpu 闲置,多线程可以在一个线程等待时,其他线程继续工作,从而充分利用 cpu。

创建线程的几种方式?

  1. 继承 Thread 类重新 run 方法

  2. 实现 runable 接口重写run 方法

  3. 实现 Callable 接口+FutureTask(有返回值)

    java
    import java.util.concurrent.Callable;
    import java.util.concurrent.FutureTask;
    
    class MyCallable implements Callable<String> {
        @Override
        public String call() {
            return "Result from Callable";
        }
    }
    
    public class Main {
        public static void main(String[] args) throws Exception {
            FutureTask<String> task = new FutureTask<>(new MyCallable());
            new Thread(task).start();
          
          //task.get()方法会阻塞
            System.out.println("Callable result: " + task.get());
        }
    }

    需要注意的是

    • callable 是一个可以产生返回值的任务。
    • 你可以通过 future 或 futureTask获取这个返回值;
    • 如果你不调用 get() 方法,主线程不会阻塞

什么是守护线程与非守护线程?如何创建?

守护线程是指在后台默默提供服务的线程。当所有用户线程(非守护线程)都结束后,jvm 会自动退出,及时守护线程还在运行。常用于垃圾回收、异步清理任务、日志、监控、心跳等后台服务线程

非守护线程(也称之为用户线程)是程序中默认创建的线程,JVM 会一直运行,知道所有非守护线程结束,才会退出。

Thread 有一个属性用户区分时候是守护线程,通过 Thread.setDaemon(true) 来设置(必须在启动之前)

线程的几种状态,如果流转的?

Java 中线程一共有 6 种状态,由java.lang.Thread.State 枚举定义:

状态名含义
NEW新建状态:线程已创建但尚未启动
RUNNABLE可运行状态:正在运行或等待 CPU 调度
BLOCKED阻塞状态:等待锁(monitor)
WAITING无限期等待:等待别的线程显式唤醒
TIMED_WAITING有时间限制的等待:如 sleep()join(1000)
TERMINATED终止状态:线程执行完毕或异常退出

生命周期:

scss
        ┌──────────┐
        │   NEW    │
        └────┬─────┘
             │ start()

        ┌────────────┐
        │ RUNNABLE   │
        └───┬─┬──────┘
            │ │
            │ └────────────┐
            ▼              ▼
      ┌─────────┐     ┌──────────────┐
      │ BLOCKED │     │ WAITING      │◄─────────┐
      └────┬────┘     └──────────────┘          │
           │               ▲                    │
           │               │                    │
           ▼               │                    │
    ┌──────────────┐       │             ┌──────────────┐
    │ TIMED_WAITING│───────┘             │TERMINATED    │
    └──────────────┘                     └──────────────┘

线程的优先级有什么用?

它的作用是"影响线程被 CPU 调度的概率"

  • 高优先级线程在理论上更有可能被操作系统线程调度器选中。
  • 但这只是一个指标,具体行为依赖与操作系统和 JVM 实现。不能依赖它来保证顺序性或者公平性。(优先级不是抢占式控制,不是强制调度!)

java 中每个线程都有一个整数优先级,取值范围是:

常量名说明
Thread.MIN_PRIORITY1最低优先级
Thread.NORM_PRIORITY5(默认)普通优先级
Thread.MAX_PRIORITY10最高优先级

可用通过以下当时设置优先级:

java
Thread t = new Thread(...);
t.setPriority(Thread.MAX_PRIORITY); // 设置最高优先级

线程优先级只是调度建议,不是强制控制,不能依赖它实现功能或控制顺序,但可用于性能调优和资源让渡。

怎么让 3 个线程按顺序执行?编程实现?

  1. 使用 join()方法,join()方法的主要作用是让当前线程等待调用 join 的线程执行完成后再继续执行

    java
    package com.hongsipeng.juc;
    
    /**
     * @author hongsipeng
     * @apiNote join 方法测试
     * @since 2025/7/30
     */
    public class JoinExample {
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                System.out.println("线程 1 开始");
            });
            // join() 方法可以让一个线程等待另一个线程执行完毕
            Thread thread2 = new Thread(() -> {
                try {
                    thread1.join();
                    System.out.println("线程 2 开始");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
    
            Thread thread3 = new Thread(() -> {
                try {
                    thread2.join();
                    System.out.println("线程 3 开始");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            thread1.start();
            thread2.start();
            thread3.start();
    
        }
    }
  2. 使用单线程池,线程池中只有一个核心线程,提交任务给线程池时,线程任务会被阻塞

    java
    package com.hongsipeng.juc;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * @author hongsipeng
     * @apiNote 单线程池控制线程按照顺序执行
     * @since 2025/7/30
     */
    public class SingleThreadExecutorExample {
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newSingleThreadExecutor();
            executorService.execute(()->{
                System.out.println("线程 1 执行");
            });
            executorService.execute(()->{
                System.out.println("线程 2 执行");
            });
            executorService.execute(()->{
                System.out.println("线程 3 执行");
            });
            executorService.shutdown(); //如果不加这个,程序不会终止
        }
    }
  3. 使用 Wait 和 notify 机制

    java
    package com.hongsipeng.juc;
    
    /**
     * @author hongsipeng
     * @apiNote wait notify example
     * @since 2025/8/3
     */
    public class WaitNotifyExample {
        private static int currentThread = 1;
    
        public static void main(String[] args) {
    
            Object lock = new Object();
            new Thread(() -> {
                synchronized (lock) {
                    while (currentThread != 1) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("Thread " + currentThread + " is over");
                    currentThread = 2;
                    lock.notifyAll();
                }
            }).start();
    
            new Thread(() -> {
                synchronized (lock) {
                    while (currentThread != 2) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("Thread " + currentThread + " is over");
                    currentThread = 3;
                    lock.notifyAll();
                }
            }).start();
    
            new Thread(() -> {
                synchronized (lock) {
                    while (currentThread != 3) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("Thread " + currentThread + " is over");
                    currentThread = 4;
                    lock.notifyAll();
                }
            }).start();
        }
    }
  4. 使用 Condition 实现

    java
    package com.hongsipeng.juc;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @author hongsipeng
     * @apiNote 使用 Condition实现线程按照顺序执行
     * @since 2025/8/4
     */
    public class ThreadOrderWithConditionTest {
        private static final Lock lock = new ReentrantLock();
        private static final Condition condition1 = lock.newCondition();
        private static final Condition condition2 = lock.newCondition();
        private static final Condition condition3 = lock.newCondition();
        private static int currentThread = 1;
    
        public static void main(String[] args) {
            new Thread(() -> {
                try {
                    lock.lock();
                    while (currentThread != 1) {
                        try {
                            condition1.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("Thread " + currentThread + " is over");
                    currentThread = 2;
                    condition2.signal();
                } finally {
                    lock.unlock();
                }
            }).start();
    
            new Thread(() -> {
                try {
                    lock.lock();
                    while (currentThread != 2) {
                        try {
                            condition2.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("Thread " + currentThread + " is over");
                    currentThread = 3;
                    condition3.signal();
                } finally {
                    lock.unlock();
                }
            }).start();
    
            new Thread(() -> {
                try {
                    lock.lock();
                    while (currentThread != 3) {
                        try {
                            condition3.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("Thread " + currentThread + " is over");
                    condition1.signal();//如果需要循环执行
                } finally {
                    lock.unlock();
                }
            }).start();
        }
    }

    相比 wait 和 notify,condition 提供了更精确的线程唤醒机制,可以为不同的等待条件创建不同的 Condition 对象。

    注意:必须在 lock 和 unlock 之间调用 condition 的方法,否则会抛出IllegalMonitorStateException 异常

  5. 使用 CountDownLatch

    java
    package com.hongsipeng.juc;
    
    import java.util.concurrent.CountDownLatch;
    
    /**
     * @author hongsipeng
     * @apiNote CountDownLatch 测试例子,控制线程按照顺序执行
     * @since 2025/8/4
     */
    public class CountDownLatchExample {
        static CountDownLatch countDownLatch1 = new CountDownLatch(1);
        static CountDownLatch countDownLatch2 = new CountDownLatch(1);
    
        public static void main(String[] args) {
            new Thread(() -> {
                System.out.println("线程 1 执行");
                countDownLatch1.countDown();
            }).start();
    
            new Thread(() -> {
                try {
                    countDownLatch1.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("线程 2 执行");
                countDownLatch2.countDown();
            }).start();
    
            new Thread(() -> {
                try {
                    countDownLatch2.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("线程 3 执行");
            }).start();
        }
    }
  6. 使用 CompletableFuture

    java
    package com.hongsipeng.juc;
    
    import java.util.concurrent.CompletableFuture;
    
    /**
     * @author hongsipeng
     * @apiNote CompletableFuture 测试类
     * @since 2025/8/4
     */
    public class CompletableFutureExample {
        public static void main(String[] args) {
            CompletableFuture.runAsync(()->{
                System.out.println("线程 1 执行");
            }).thenRun(()->{
                System.out.println("线程 2 执行");
            }).thenRun(()->{
                System.out.println("线程 3 执行");
            }).join();
        }
    }

    CompletableFuture 是 java8 引入的一个强大的异步编程工具,属于 java.util.concurrent包下,它是对 future 的增强版,支持链式调用,组合异步调用,异常处理等。详细介绍请看本文后半部分。

join 方法有什么用?什么原理?编程实现?

join()方法是

如何让一个线程休眠?

启动一个线程是 start 还是 run? 两者有什么区别?

一个线程多次调用 start 会发生什么?

线程 sleep 和 wait 的区别?

Thread.yield方法有什么用?yield 方法和 sleep 方法的区别?编程实现 yield 的例子?

怎么理解 java 中的线程中断?中断和stop 的区别?编程实现一个线程中断的例子?

你怎么理解多线程分组?编程实现一个线程分组的例子

如何理解 wait、notify、notifyAll?为什么这三个方法不是 Thread 类中的,而是 Object 类中的,使用过程中有什么需要注意的吗?

Wait、notify、notifyAll 是 Object 类中的重要方法,用于线程之间的协调通信,它们是 java 多线程编程中实现线程间下作的基础机制。

wait方法:让当前线程进入等待状态,并释放所持有的锁,直到被唤醒

  • 使当前线程进入等待状态,释放对象的锁
  • 必须在同步代码块或同步方法中调用(即必须先获得对象的监视器锁(对象的监视器锁指的是 jvm级别的锁的内置实现))
  • 调用后线程会一直等待,直到其他线程调用对象的 notify 或 notifyAll()方法
  • 有三个重载版本
    • wait() - 无限等待
    • wait(long timeOut) - 等待指令毫秒数
    • wait(long timeOut, int nanos) - 更精确的等待时间

notify 方法:随机唤醒一个正在等待该对象锁的线程

  • 唤醒一个正在等待该对象监视器的线程
  • 同样该方法必须在同步代码块或同步方法中调用
  • 如果有多个线程在等待,JVM 会任意选择一个线程唤醒,被唤醒的线程不会立刻执行,会重新竞争获取锁。

notifyAll 方法:唤醒所有正在等待该对象锁的进程,让它们竞争锁

  • 唤醒所有正在等待该对象监视器的线程
  • 这些线程将竞争获取锁对象
  • 通过比 notify()更安全,避免某些线程永远等待

为什么这三个方法定义在 Object 类,而不是 Thread类?

这是一个设计上的关键决策,原因如下

  1. 锁是基于对象的(Monitor 机制)

    • java 的同步机制是基于对象的,每个对象都有一个内置锁(Monitor)
    • wait()、notify()和 notifyAll() 是锁的协作机制,必须和 Synchronized 配合使用,如果它们定义在 Thread类,就无法和对象锁直接关联,导致设计混乱
  2. 线程可以等待多个不同的对象

    • 一个线程可以同时持有多个对象的锁,并在不同条件下等待:

      java
      synchronized (lock1) {
          synchronized (lock2) {
              lock1.wait();  // 释放 lock1,但仍持有 lock2
          }
      }
    • 如果 wait() 是 Thread 的方法,就无法指定等待那个对象的锁。

  3. notify 需要明确目标对象,如果 notify 是 Thread 的方法,就无法精确控制唤醒哪个锁上的线程

  4. 更符合面向对象的设计

注意事项

  1. 必须在同步代码块中使用,否则会抛出 IllegalMonitorStateException
  2. 通常 Wait() 方法应该放在循环中检查条件(使用 While 而不是 if)防止虚假唤醒
  3. notify 和 notifyAll 的使用取决于具体使用场景
  4. 被 notify()或 notifyAll()唤醒的线程在重新获取锁后,会从 wait() 调用之后继续执行,而不是从头开始执行同步块(wait的时候或保存上下文环境)
  5. 从 java 5 开始,更推进使用 java.util.concurrent 包中的高级同步工具,如 Conditon,BlockingQueue 等)

线程池中的线程抛出了异常,如何处理?

同步和异步、阻塞和非阻塞的区别?

什么是死锁?如何避免死锁?

什么是活锁?什么是无锁?

编程实现一个多线程死锁的例子?

AtomicInteger 的底层实现是怎样的?

什么是 CAS?CAS 有什么缺点?CAS 底层使用了哪个操作类?

CAS 在 JDK 中有哪些应用?

用伪代码写一个 CAS 算法的核心

多线程情况下,进行数字累加(count++)需要注意什么

有了 AotimicInteger,为什么 JDK 又出了个 LongAdder?不同的使用场景?

LongAdder 为什么性能更好,原理是什么?那有没有什么缺点呢?

LongAccumulator是什么?和 LongAdder怎么选?

并行和并发的区别?

为什么不推荐使用 stop 停止线程?如何优雅的终止一个线程?

什么是重入锁?重入锁有哪些重要的方法?如何使用?

如何理解重入锁的重入?最多可以重入多少次?

Synchronized 是重入锁吗?是怎么实现的?

Synchronized 与 ReetrantLock 的区别?

Synchronized 同步锁有哪几种用法?

Synchronized 锁的是什么?Synchronized 关键字的底层实现原理?

Synchronized 可以保证原子性吗?可以保证可见性吗?可以保证有序性吗?如何实现的?

Java 对 Synchronized 进行了哪些优化?

什么是读写锁?有没有比 ReedWriteLock 读写锁更快的锁?

有哪些锁优化的方式?

什么是自旋锁?

什么是锁消除?

什么是锁粗化?

什么是锁升级?锁升级的过程是怎么样的?

什么是偏向锁?

什么是轻量级锁?

什么是重量级锁?

什么是线程池?使用线程池有什么好处?

谈谈多线程中的 ExecutorSevice 接口和 ThreadPoolExecutor类?

线程池的工作流程是怎样的?

线程是 ExecutorService 和 Executors 的区别?

Java 里面有哪些内置的线程池?

为什么阿里巴巴开发手册不让用 Executors 工具类来创建线程池?

线程池的拒绝策略有哪几种?

线程池 submit 和 execute 有什么区别?

如何查看线程池的运行状态?

如何设置线程池的大小?

如何关闭线程池?

谈谈多线程中的 CompletionService 接口?CompletionService 用完需要关闭吗?怎么关闭?

谈谈多线程中的 ExecutorCompletionService类?

Java 实现异步编程有什么方案?

Future 是什么?编程实现一个 Future 的使用例子?

FutureTask 是什么?编程实现一个 FutureTask 的例子?

Future 和 FutureTask的区别?

CompletableFuture 是什么?有哪些应用场景?

CompletableFuture 和 Future 的区别?

CompletableFuture 的 get 和 join 的区别?

CompletableFuture 的 thenApply 和 thenCompose 区别?

CompletableFuture 如何处理异常?

CompletableFuture默认使用的什么线程池?

CompletableFuture 如何自定义线程池?

CompletableFuture 如何优化性能?

编程实现一个 CompletableFuture 的使用例子?

谈谈多线程中的 CompletionStage 接口?

AQS 是什么?底层原理什么什么?

Fork Join框架有什么用?

Fork Join 框架的运行流程?

Fork Join 框架底层什么机制?

Fork Join 框架核心类有哪些?

使用 Fork Join 框架有什么需要注意的?

编程实现使用 Fork Join 框架的例子?

ThreadLocal 有什么用?

ThredLocal 的底层实现怎么样的?

ThreadLocal中的 Key 为什么需要设计为弱引用?

ThreadLocal 为什么会导致内存溢出?

编程实现 ThreadLocal 的使用例子?

ThreadLocal 父子线程如何传递数据?

ThreadLocal 与 InheritableThreadLocal 的区别?

InheritableThreadLocal 的底层实现原理?

volatile 关键字有什么用?

volatile 有哪些应用场景?

volatile 可以保证原子性吗?可以保证可见性吗?可以保证有序性吗?怎么实现的?

Volatile 可以代替 Synchronized 使用吗?为什么?

CountDownLatch 有什么用?编程实现一个 CountDownLatch 的使用示例?

CyclicBarrier 有什么用?编程实现一个例子吗?

CountDownLatch 与 CyclicBarrier 的区别?

Semaphore 有什么用?编程实现一个使用用例?

Exchanger 有什么用?

编程实现两个线程彼此交换数据的例子?

LockSupport 有什么?

LockSupport 和 wait-notify-notiofyAll有什么区别?

编程实现一个 LockSupport 阻塞唤醒线程的使用示例

Java 中原子操作的类有哪些?

什么是 ABA 问题?如何解决 ABA 问题?

什么是 Happens-Before 原则?Happens-Before 原则有哪些?

Java并发容器有哪些?

什么是协程?有了多线程为什么还有搞出协程?

Java 支持协程吗?支持协程的框架有哪些?

SimpleDateFotmat是线程安全的吗?为什么?如何解决?

什么是 ParallelStream?和 Strem 的区别?底层原理什么?

ParallelStream 是线程安全的吗?默认启动了多少个线程?如何修改默认线程数?

本站访客数 人次 本站总访问量