当前位置: 首页 > news >正文

多线程并发篇面试题

目录

  1. 多线程基础
  2. 线程安全
  3. 锁机制
  4. 并发工具类
  5. 线程池
  6. JUC包
  7. 内存模型
  8. 性能优化

多线程基础

1. 什么是线程?线程和进程的区别?

答案:

线程:CPU调度的基本单位,是进程内的执行单元。
进程:操作系统资源分配的基本单位。

主要区别:

  • 进程

    • 独立的内存空间
    • 进程间通信复杂(需要IPC机制)
    • 创建和销毁开销大
  • 线程

    • 共享进程内存空间
    • 线程间通信简单(共享内存)
    • 创建和销毁开销小

示例:

Thread thread = new Thread(() -> System.out.println("Hello Thread"));
thread.start();

2. 创建线程的方式有哪些?

答案:

  1. 继承Thread类
class MyThread extends Thread {@Overridepublic void run() {System.out.println("Thread running");}
}
  1. 实现Runnable接口
class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("Runnable running");}
}
  1. 实现Callable接口
class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {return "Callable result";}
}
  1. 使用线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> System.out.println("Pool thread"));

3. 线程的生命周期?

答案:

线程状态:

  1. NEW(新建)- 线程创建但未启动
  2. RUNNABLE(可运行)- 线程正在JVM中执行
  3. BLOCKED(阻塞)- 线程被阻塞等待监视器锁
  4. WAITING(等待)- 线程无限期等待另一个线程执行特定操作
  5. TIMED_WAITING(超时等待)- 线程等待另一个线程执行操作,但有时限
  6. TERMINATED(终止)- 线程执行完毕

状态转换:

NEW → RUNNABLE ⇄ BLOCKED/WAITING/TIMED_WAITING → TERMINATED

4. 线程的优先级?

答案:

优先级范围:1-10,默认优先级为5。

设置优先级:

thread.setPriority(Thread.MAX_PRIORITY);  // 设置最高优先级(10)
thread.setPriority(Thread.MIN_PRIORITY);   // 设置最低优先级(1)
thread.setPriority(Thread.NORM_PRIORITY);  // 设置默认优先级(5)

注意事项:

  • 优先级只是建议,不保证执行顺序
  • 最终执行顺序依赖操作系统调度
  • 不要过度依赖线程优先级控制程序逻辑

线程安全

5. 什么是线程安全?

答案:

线程安全 指多线程环境下,程序能够正确处理共享数据,不会出现数据不一致或竞态条件。

线程安全级别:

  1. 不可变(Immutable)- String、Integer等不可变对象
  2. 绝对线程安全(Absolutely Thread-Safe)- Vector、Hashtable等
  3. 相对线程安全(Relatively Thread-Safe)- 大部分JDK类,如ArrayList、HashMap
  4. 线程兼容(Thread-Compatible)- 需要外部同步
  5. 线程对立(Thread-Hostile)- 无论是否同步都无法在多线程环境使用

6. 什么是竞态条件?

答案: 竞态条件 指多个线程访问共享资源时,执行结果依赖于线程执行的时序。

示例:

public class Counter {private int count = 0;public void increment() {count++; // 非原子操作,存在竞态条件}
}

解决方案: 使用synchronized、volatile、原子类等同步机制。

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

答案:

死锁 指两个或多个线程互相等待对方释放资源,导致程序无法继续执行。

死锁的四个必要条件:

  1. 互斥条件:资源不能被多个线程同时使用
  2. 请求和保持条件:线程持有资源的同时请求其他资源
  3. 不剥夺条件:资源不能被强制释放
  4. 循环等待条件:线程形成循环等待链

避免死锁的方法:

  1. 避免嵌套锁:尽量不要在持有一个锁的情况下获取另一个锁
  2. 按顺序获取锁:所有线程按照相同的顺序获取锁
  3. 使用超时锁:使用tryLock(timeout)避免无限等待
  4. 死锁检测和恢复:定期检测死锁并采取恢复措施

示例:

// 按固定顺序获取锁,避免死锁
synchronized(lock1) {synchronized(lock2) {// 临界区代码}
}

锁机制

8. synchronized关键字的作用?

答案:

synchronized 是Java内置的同步机制,用于实现线程安全,确保同一时刻只有一个线程执行同步代码。

三种使用方式:

  1. 同步方法
public synchronized void method() {// 同步方法
}
  1. 同步代码块
synchronized(object) {// 同步代码块
}
  1. 静态同步方法
public static synchronized void method() {// 静态同步方法
}

锁对象说明:

  • 实例方法:锁对象是this(当前实例对象)
  • 静态方法:锁对象是Class对象(类对象)
  • 同步代码块:锁对象是括号中指定的对象

9. synchronized的底层原理?

答案:

底层实现:基于JVM的monitor(监视器)机制,通过monitorentermonitorexit指令实现。

锁升级过程

无锁 → 偏向锁 → 轻量级锁 → 重量级锁

各级锁的特点:

  • 偏向锁:只有一个线程访问时,记录线程ID,避免CAS操作,性能最好
  • 轻量级锁:多线程竞争不激烈时,使用CAS操作,避免操作系统互斥量
  • 重量级锁:多线程竞争激烈时,使用操作系统互斥量(Mutex),会导致线程阻塞

10. volatile关键字的作用?

答案:

volatile 保证变量的可见性和有序性,但不保证原子性。

三大特性:

  1. 可见性:一个线程修改volatile变量,其他线程立即可见
  2. 有序性:禁止指令重排序(通过内存屏障实现)
  3. 不保证原子性:复合操作(如i++)仍需要同步

使用场景:

  • 状态标志位
  • 双重检查锁定(DCL单例模式)
  • 独立观察变量

示例:

private volatile boolean flag = false;// 线程1
public void setFlag() {flag = true;  // 写操作对其他线程立即可见
}// 线程2
public void checkFlag() {if (flag) {  // 能立即看到线程1的修改// 执行逻辑}
}

11. ReentrantLock和synchronized的区别?

答案:

ReentrantLock 是JUC包提供的可重入锁,相比synchronized提供了更多高级功能。

详细区别对比:

特性synchronizedReentrantLock
锁获取自动获取和释放手动获取和释放
中断响应不支持支持
超时获取不支持支持
公平锁非公平可选择公平或非公平
条件变量单一条件多个条件
性能JVM优化,性能好需要手动优化

示例:

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {// 临界区
} finally {lock.unlock();
}

12. 什么是读写锁?

答案:

读写锁(ReentrantReadWriteLock)允许多个线程同时读取,但写操作是独占的。

核心特性:

  • 读锁(共享锁):多个线程可以同时持有读锁
  • 写锁(排他锁):只有一个线程可以持有写锁,且写锁排斥所有读锁

使用示例:

ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();// 读操作
rwLock.readLock().lock();
try {// 读取数据
} finally {rwLock.readLock().unlock();
}// 写操作
rwLock.writeLock().lock();
try {// 修改数据
} finally {rwLock.writeLock().unlock();
}

使用场景:读多写少的场景,如缓存、配置管理等。

优势:提高并发性能,多个读操作可以并发执行,不会相互阻塞。


并发工具类

13. CountDownLatch详解(源码+应用场景)

答案:

CountDownLatch 是一个同步辅助类,允许一个或多个线程等待其他线程完成操作。它基于AQS(AbstractQueuedSynchronizer)的共享锁模式实现。

核心特性:

  • 一次性使用:计数到达零后不能重置
  • 共享锁模式:多个线程可以同时等待
  • 倒计时功能:从初始计数开始递减到0

核心方法:

  • await():等待计数器到达0
  • countDown():计数器减1
  • getCount():获取当前计数

基础示例:

// 创建CountDownLatch,初始计数为3
CountDownLatch latch = new CountDownLatch(3);// 工作线程
for (int i = 0; i < 3; i++) {new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + " 正在执行任务");Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + " 任务完成");} finally {latch.countDown();  // 计数器减1}}).start();
}// 主线程等待
System.out.println("主线程等待所有任务完成...");
latch.await();  // 阻塞,直到计数器为0
System.out.println("所有任务完成,主线程继续执行");

典型应用场景:

场景1:系统初始化等待

public class SystemInitializer {private final CountDownLatch initLatch = new CountDownLatch(3);public void initialize() throws InterruptedException {// 初始化数据库new Thread(() -> {try {System.out.println("初始化数据库...");Thread.sleep(2000);System.out.println("数据库初始化完成");} finally {initLatch.countDown();}}).start();// 初始化缓存new Thread(() -> {try {System.out.println("初始化缓存...");Thread.sleep(1500);System.out.println("缓存初始化完成");} finally {initLatch.countDown();}}).start();// 初始化配置new Thread(() -> {try {System.out.println("加载配置...");Thread.sleep(1000);System.out.println("配置加载完成");} finally {initLatch.countDown();}}).start();// 等待所有初始化完成initLatch.await();System.out.println("系统初始化完成,可以对外提供服务");}
}

场景2:并发测试(模拟高并发)

public class ConcurrentTest {public void testConcurrent() throws InterruptedException {int threadCount = 100;CountDownLatch startLatch = new CountDownLatch(1);  // 开始信号CountDownLatch endLatch = new CountDownLatch(threadCount);  // 完成信号// 创建100个线程for (int i = 0; i < threadCount; i++) {new Thread(() -> {try {startLatch.await();  // 等待开始信号// 执行并发测试performTest();} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {endLatch.countDown();}}).start();}// 所有线程就位后,同时启动System.out.println("所有线程准备就绪,开始并发测试");startLatch.countDown();  // 释放开始信号// 等待所有线程完成endLatch.await();System.out.println("并发测试完成");}private void performTest() {// 测试逻辑}
}

源码实现原理(简化版):

public class CountDownLatch {// 内部同步器,基于AQSprivate static final class Sync extends AbstractQueuedSynchronizer {Sync(int count) {setState(count);  // 设置初始计数}// 尝试获取共享锁:计数为0返回1,否则返回-1protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}// 尝试释放共享锁:递减计数,为0时返回trueprotected boolean tryReleaseShared(int releases) {for (;;) {int c = getState();if (c == 0) return false;  // 已经为0,不能继续减int nextc = c - 1;if (compareAndSetState(c, nextc))  // CAS更新计数return nextc == 0;  // 返回是否到达0}}}private final Sync sync;public CountDownLatch(int count) {this.sync = new Sync(count);}public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);  // 获取共享锁}public void countDown() {sync.releaseShared(1);  // 释放共享锁,计数减1}
}

vs wait/notify 的优势:

  1. 代码简洁:无需手动管理同步代码块和条件判断
  2. 线程安全:内置线程安全保证,无需额外同步
  3. 无虚假唤醒:不会出现wait/notify的虚假唤醒问题
  4. 性能更好:基于AQS的无锁算法,性能优于synchronized
  5. 易于维护:代码清晰,不易出错

14. CyclicBarrier的作用?

答案:

CyclicBarrier(循环屏障)是一个同步辅助类,允许多个线程相互等待,直到所有线程都到达某个公共屏障点。

核心特性:

  • 可重复使用:所有线程到达屏障后,屏障会重置,可以重复使用
  • 相互等待:所有线程必须到达屏障点才能继续执行
  • 回调函数:可以设置所有线程到达后执行的回调动作

与CountDownLatch区别:

特性CountDownLatchCyclicBarrier
使用次数一次性可重复使用
计数方向递减到0递增到指定值
等待模式一个或多个线程等待多个线程相互等待
重置能力不支持支持reset()

示例:

// 创建CyclicBarrier,3个线程到达后执行回调
CyclicBarrier barrier = new CyclicBarrier(3, () -> {System.out.println("所有线程到达屏障,执行汇总任务");
});// 工作线程
for (int i = 0; i < 3; i++) {new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + " 开始执行");Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + " 到达屏障");barrier.await();  // 等待其他线程到达System.out.println(Thread.currentThread().getName() + " 继续执行");} catch (Exception e) {e.printStackTrace();}}).start();
}

15. Semaphore的作用?

答案:

Semaphore(信号量)是一个计数信号量,用于控制同时访问特定资源的线程数量。

核心特性:

  • 许可控制:通过许可证(permits)控制并发访问数量
  • 公平性:支持公平和非公平模式
  • 可中断:支持中断和超时获取

使用场景:

  • 限制并发访问数量(如数据库连接池)
  • 实现资源池(如线程池、对象池)
  • 控制流量(如限流器)

示例:

// 创建Semaphore,允许3个线程同时访问
Semaphore semaphore = new Semaphore(3);// 访问资源
for (int i = 0; i < 10; i++) {new Thread(() -> {try {semaphore.acquire();  // 获取许可System.out.println(Thread.currentThread().getName() + " 获取许可,开始访问资源");Thread.sleep(2000);  // 模拟资源访问System.out.println(Thread.currentThread().getName() + " 释放许可");} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {semaphore.release();  // 释放许可}}).start();
}

16. Exchanger的作用?

答案:

Exchanger 是一个同步点,用于两个线程之间交换数据。

核心特性:

  • 双向交换:两个线程互相交换数据
  • 同步点:两个线程必须同时到达才能交换
  • 类型安全:泛型支持,保证类型安全

使用场景:

  • 两个线程需要交换数据
  • 生产者消费者模式
  • 数据校验(两个线程处理相同数据,交换结果进行校验)

示例:

Exchanger<String> exchanger = new Exchanger<>();// 线程1
new Thread(() -> {try {String data = "来自线程1的数据";System.out.println("线程1准备交换数据:" + data);String received = exchanger.exchange(data);  // 交换数据,阻塞等待线程2System.out.println("线程1收到数据:" + received);} catch (InterruptedException e) {Thread.currentThread().interrupt();}
}).start();// 线程2
new Thread(() -> {try {String data = "来自线程2的数据";System.out.println("线程2准备交换数据:" + data);String received = exchanger.exchange(data);  // 交换数据,阻塞等待线程1System.out.println("线程2收到数据:" + received);} catch (InterruptedException e) {Thread.currentThread().interrupt();}
}).start();

线程池

17. 什么是线程池?为什么使用线程池?

答案:

线程池 是预先创建一定数量的线程,用于执行任务的管理机制。

四大优势:

  1. 降低资源消耗

    • 避免频繁创建和销毁线程
    • 减少内存开销和GC压力
  2. 提高响应速度

    • 任务到达时无需等待线程创建
    • 线程已经就绪,可以立即执行
  3. 提高线程可管理性

    • 统一管理线程数量、状态
    • 避免线程数量失控
  4. 提供更多功能

    • 支持定时执行、定期执行
    • 支持任务队列、拒绝策略等

18. 线程池的核心参数?

答案:

ThreadPoolExecutor七大核心参数:

  1. corePoolSize(核心线程数)

    • 线程池中始终保持活跃的线程数量
    • 即使线程空闲也不会被销毁
  2. maximumPoolSize(最大线程数)

    • 线程池中允许的最大线程数量
    • 当队列满时,会创建新线程直到达到最大值
  3. keepAliveTime(线程空闲时间)

    • 非核心线程的空闲存活时间
    • 超过此时间的空闲线程会被销毁
  4. unit(时间单位)

    • keepAliveTime的时间单位
    • TimeUnit枚举值(SECONDS、MINUTES等)
  5. workQueue(工作队列)

    • 存储待执行任务的阻塞队列
    • 常用:LinkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue
  6. threadFactory(线程工厂)

    • 用于创建新线程
    • 可以自定义线程名称、优先级等
  7. handler(拒绝策略)

    • 当队列满且线程数达到最大值时的处理策略
    • 四种默认策略:AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy

示例:

ThreadPoolExecutor executor = new ThreadPoolExecutor(5,  // 核心线程数10, // 最大线程数60L, // 空闲时间TimeUnit.SECONDS, // 时间单位new LinkedBlockingQueue<>(100), // 工作队列new ThreadFactory() {private final AtomicInteger threadNumber = new AtomicInteger(1);@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r, "MyPool-thread-" + threadNumber.getAndIncrement());t.setDaemon(false);t.setPriority(Thread.NORM_PRIORITY);return t;}}, new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);

19. 线程池的拒绝策略?

答案:

当线程池无法接受新任务时(队列满且线程数达到最大值),会触发拒绝策略。

四种默认拒绝策略:

  1. AbortPolicy(默认策略)

    • 直接抛出RejectedExecutionException异常
    • 让调用者感知到任务被拒绝
  2. CallerRunsPolicy(调用者运行策略)

    • 由调用线程(提交任务的线程)直接执行任务
    • 降低任务提交速度,起到负反馈作用
  3. DiscardPolicy(丢弃策略)

    • 静默丢弃任务,不抛出异常
    • 可能导致任务丢失,需谨慎使用
  4. DiscardOldestPolicy(丢弃最老任务策略)

    • 丢弃队列中最老的任务
    • 然后重新提交当前任务

自定义拒绝策略:

new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {// 自定义处理逻辑// 例如:记录日志、存入数据库、放入MQ等System.err.println("任务被拒绝: " + r.toString());// 可以选择性地处理任务}
}

20. 常见的线程池类型?

答案:

Executors提供的四种线程池:

  1. newFixedThreadPool(固定大小线程池)

    • 核心线程数 = 最大线程数
    • 使用无界队列LinkedBlockingQueue
    • 适合:负载较重的服务器
  2. newCachedThreadPool(缓存线程池)

    • 核心线程数 = 0,最大线程数 = Integer.MAX_VALUE
    • 使用SynchronousQueue(不存储任务)
    • 适合:执行大量短期异步任务
  3. newSingleThreadExecutor(单线程池)

    • 只有一个线程的线程池
    • 保证任务按提交顺序串行执行
    • 适合:需要顺序执行的场景
  4. newScheduledThreadPool(定时任务线程池)

    • 支持定时及周期性任务执行
    • 适合:定时任务场景

⚠️ 重要提示:
生产环境不推荐使用Executors创建线程池,原因:

  • newFixedThreadPoolnewSingleThreadExecutor使用无界队列,可能导致OOM
  • newCachedThreadPool最大线程数为Integer.MAX_VALUE,可能创建大量线程导致OOM

**推荐做法:**使用ThreadPoolExecutor自定义参数,明确指定队列大小和最大线程数。


JUC包

21. 什么是CAS?CAS的ABA问题?

答案:

CAS(Compare And Swap)是一种无锁算法,通过比较和交换实现原子操作。

CAS三要素:

  • 内存位置(V):要更新的变量
  • 预期原值(A):期望的当前值
  • 新值(B):要设置的新值

CAS操作流程:

// 伪代码
boolean compareAndSwap(V, A, B) {if (V == A) {  // 如果当前值等于期望值V = B;     // 则更新为新值return true;}return false;  // 否则更新失败
}// 实际使用
AtomicInteger atomicInt = new AtomicInteger(0);
atomicInt.compareAndSet(0, 1);  // 期望值为0,更新为1

ABA问题:

  • 问题描述:变量从A变为B,再变回A,CAS认为没有变化,但实际已经被修改过
  • 影响场景:链表、栈等数据结构操作

解决方案:

  1. 使用版本号:AtomicStampedReference(带版本戳的原子引用)
AtomicStampedReference<Integer> atomicRef = new AtomicStampedReference<>(0, 0);
int stamp = atomicRef.getStamp();
atomicRef.compareAndSet(0, 1, stamp, stamp + 1);  // 同时比较值和版本号
  1. 使用时间戳:AtomicMarkableReference(带标记的原子引用)

22. 原子类的实现原理?

答案: 原子类 基于CAS操作实现,如AtomicInteger、AtomicLong等。

实现原理: 使用Unsafe类的CAS操作,底层调用CPU的CAS指令。

示例:

AtomicInteger atomicInt = new AtomicInteger(0);
atomicInt.incrementAndGet(); // 原子递增
atomicInt.compareAndSet(0, 1); // CAS操作

23. ConcurrentHashMap的实现原理?

答案: ConcurrentHashMap 是线程安全的HashMap实现。

JDK 1.7实现: 使用分段锁(Segment),每个Segment包含一个HashEntry数组。

JDK 1.8实现: 使用CAS + synchronized,数组 + 链表 + 红黑树结构。

优势: 高并发性能,读操作无锁,写操作使用CAS和synchronized。

24. BlockingQueue的实现原理?

答案: BlockingQueue 是支持阻塞操作的队列接口。

常见实现: 1. ArrayBlockingQueue (有界数组队列) 2. LinkedBlockingQueue (有界链表队列) 3. PriorityBlockingQueue (优先级队列) 4. DelayQueue (延迟队列) 5. SynchronousQueue (同步队列)

阻塞操作:

put()    // 阻塞插入
take()   // 阻塞获取
offer()  // 非阻塞插入
poll()   // 非阻塞获取

内存模型

25. 什么是JMM?

答案:

JMM(Java Memory Model,Java内存模型)定义了Java程序中各种变量的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。

内存区域:

  • 主内存(Main Memory):所有线程共享,存储所有变量
  • 工作内存(Working Memory):每个线程私有,存储该线程使用的变量副本

八种内存交互操作:

  1. lock(锁定):作用于主内存的变量
  2. unlock(解锁):作用于主内存的变量
  3. read(读取):从主内存读取变量到工作内存
  4. load(载入):将read的变量值放入工作内存的变量副本
  5. use(使用):从工作内存读取变量值
  6. assign(赋值):将新值赋给工作内存的变量
  7. store(存储):将工作内存的变量值传送到主内存
  8. write(写入):将store的变量值写入主内存

26. 什么是happens-before?

答案:

happens-before 是JMM定义的内存可见性规则,用于确定操作之间的内存可见性。

核心含义:
如果操作A happens-before 操作B,那么A的执行结果对B可见,且A的执行顺序排在B之前。

八大happens-before规则:

  1. 程序顺序规则(Program Order Rule)

    • 同一个线程内,前面的操作 happens-before 后面的操作
  2. 监视器锁规则(Monitor Lock Rule)

    • unlock操作 happens-before 后续对同一个锁的lock操作
  3. volatile变量规则(Volatile Variable Rule)

    • volatile写操作 happens-before 后续对该变量的读操作
  4. 线程启动规则(Thread Start Rule)

    • Thread.start() happens-before 该线程的每一个动作
  5. 线程终止规则(Thread Termination Rule)

    • 线程的所有操作 happens-before 其他线程检测到该线程终止
  6. 线程中断规则(Thread Interruption Rule)

    • 线程interrupt()调用 happens-before 被中断线程检测到中断事件
  7. 对象终结规则(Finalizer Rule)

    • 对象的构造函数执行结束 happens-before finalize()方法
  8. 传递性(Transitivity)

    • 如果A happens-before B,B happens-before C,则A happens-before C

27. 什么是内存屏障?

答案:

内存屏障(Memory Barrier)是CPU指令,用于防止指令重排序和保证内存可见性。

四种类型:

  1. LoadLoad屏障(读-读屏障)

    • 确保Load1的数据装载先于Load2及后续装载指令
    • 格式:Load1; LoadLoad; Load2
  2. StoreStore屏障(写-写屏障)

    • 确保Store1的数据刷新到主内存先于Store2及后续存储指令
    • 格式:Store1; StoreStore; Store2
  3. LoadStore屏障(读-写屏障)

    • 确保Load1的数据装载先于Store2及后续存储指令
    • 格式:Load1; LoadStore; Store2
  4. StoreLoad屏障(写-读屏障)

    • 确保Store1的数据刷新到主内存先于Load2及后续装载指令
    • 格式:Store1; StoreLoad; Load2
    • 开销最大的屏障

作用:

  • 防止指令重排序
  • 保证内存可见性
  • 确保并发程序的正确性

性能优化

28. 如何优化多线程性能?

答案:

六大优化策略:

  1. 减少锁的粒度

    • 使用细粒度锁替代粗粒度锁
    • 例如:ConcurrentHashMap的分段锁
  2. 减少锁的持有时间

    • 尽快释放锁,缩小同步代码块范围
    • 将耗时操作移到锁外执行
  3. 使用无锁数据结构

    • 使用CAS操作替代锁
    • 使用原子类(AtomicInteger等)
    • 使用并发容器(ConcurrentHashMap等)
  4. 合理使用线程池

    • 避免频繁创建和销毁线程
    • 根据任务特性选择合适的线程池配置
  5. 使用读写锁

    • 读多写少场景使用ReentrantReadWriteLock
    • 允许多个读操作并发执行
  6. 避免锁竞争

    • 减少同步代码块
    • 使用ThreadLocal避免共享
    • 使用不可变对象

29. 什么是伪共享?如何避免?

答案:

伪共享(False Sharing)指多个线程访问同一缓存行(Cache Line)的不同变量,导致缓存行在CPU核心之间频繁同步,严重影响性能。

产生原因:

  • CPU缓存行通常是64字节
  • 多个变量可能位于同一缓存行
  • 一个变量被修改,整个缓存行失效,影响其他变量

避免方法:

  1. 使用@Contended注解(JDK 8+)
public class PaddedData {@Contended  // 填充缓存行,避免伪共享public volatile long value1;@Contendedpublic volatile long value2;
}
  1. 手动填充字节
public class PaddedData {public volatile long value;private long p1, p2, p3, p4, p5, p6, p7;  // 填充56字节
}
  1. 重新排列字段

    • 将可能被不同线程访问的字段分开
    • 避免它们位于同一缓存行
  2. 使用局部变量

    • 减少对共享变量的访问
    • 使用局部变量累加,最后再写回共享变量

30. 如何调试多线程问题?

答案:

五种调试方法:

  1. 使用日志

    • 记录线程执行状态、时间戳
    • 使用线程名称标识不同线程
    • 记录关键操作和状态变化
  2. 使用调试器

    • 设置断点,观察变量状态
    • 查看线程堆栈信息
    • 单步执行,观察执行流程
  3. 使用线程转储(Thread Dump)

    • 使用jstack命令获取线程堆栈
    • 分析死锁、线程状态等问题
    • 命令:jstack <pid> > thread_dump.txt
  4. 使用性能分析工具

    • JProfiler:专业的性能分析工具
    • VisualVM:JDK自带的可视化工具
    • JConsole:监控线程、内存、CPU等
  5. 使用并发测试工具

    • JCStress:并发压力测试工具
    • ThreadSanitizer:线程安全检测工具
    • FindBugs/SpotBugs:静态代码分析

常见问题及排查:

  • 死锁:使用jstack查看线程堆栈
  • 活锁:观察线程状态变化
  • 饥饿:检查线程优先级和调度
  • 竞态条件:使用断点调试,观察共享变量
  • 内存泄漏:使用内存分析工具(MAT、JProfiler)

31. 多线程环境下如何避免内存泄露?

答案: 内存泄露 指程序在运行过程中,不再使用的对象无法被垃圾回收器回收,导致内存占用持续增长。

多线程环境下的内存泄露原因:

  1. 线程未正确关闭
// 错误示例:线程未正确关闭
Thread thread = new Thread(() -> {while (true) {// 长时间运行的任务}
});
thread.start();
// 忘记调用 thread.interrupt() 或设置停止标志
  1. 线程池未正确关闭
// 错误示例:线程池未关闭
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> {// 任务执行
});
// 忘记调用 executor.shutdown()
  1. 监听器未移除
// 错误示例:监听器未移除
public class EventManager {private List<EventListener> listeners = new ArrayList<>();public void addListener(EventListener listener) {listeners.add(listener);}// 缺少移除监听器的方法// public void removeListener(EventListener listener) {//     listeners.remove(listener);// }
}
  1. 静态集合持有对象引用
// 错误示例:静态集合持有对象引用
public class CacheManager {private static Map<String, Object> cache = new HashMap<>();public void put(String key, Object value) {cache.put(key, value); // 对象永远不会被回收}// 缺少清理方法
}

避免内存泄露的方法:

  1. 正确管理线程生命周期
// 正确示例:使用标志位控制线程停止
public class WorkerThread extends Thread {private volatile boolean running = true;@Overridepublic void run() {while (running) {// 执行任务}}public void stopWorker() {running = false;}
}// 使用
WorkerThread worker = new WorkerThread();
worker.start();
// 需要停止时
worker.stopWorker();
  1. 正确关闭线程池
// 正确示例:正确关闭线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
try {executor.submit(() -> {// 任务执行});
} finally {executor.shutdown(); // 关闭线程池try {if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {executor.shutdownNow(); // 强制关闭}} catch (InterruptedException e) {executor.shutdownNow();}
}
  1. 使用弱引用
// 正确示例:使用弱引用避免内存泄露
public class WeakReferenceCache {private Map<String, WeakReference<Object>> cache = new HashMap<>();public void put(String key, Object value) {cache.put(key, new WeakReference<>(value));}public Object get(String key) {WeakReference<Object> ref = cache.get(key);return ref != null ? ref.get() : null;}
}
  1. 及时清理资源
// 正确示例:及时清理资源
public class ResourceManager {private List<Resource> resources = new ArrayList<>();public void addResource(Resource resource) {resources.add(resource);}public void removeResource(Resource resource) {resources.remove(resource);resource.close(); // 及时关闭资源}public void cleanup() {for (Resource resource : resources) {resource.close();}resources.clear();}
}
  1. 使用ThreadLocal的正确方式
// 正确示例:正确使用ThreadLocal
public class ThreadLocalManager {private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>() {@Overrideprotected SimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd");}};public static String formatDate(Date date) {return dateFormat.get().format(date);}// 在适当的时候清理ThreadLocalpublic static void cleanup() {dateFormat.remove();}
}
  1. 避免循环引用
// 正确示例:避免循环引用
public class Node {private String data;private Node next;public void setNext(Node next) {this.next = next;}// 提供清理方法public void clear() {this.next = null;this.data = null;}
}

内存泄露检测工具:

  • JProfiler - 专业的内存分析工具
  • VisualVM - JDK自带的性能分析工具
  • MAT (Memory Analyzer Tool) - Eclipse内存分析工具
  • JConsole - JDK自带的监控工具

最佳实践:

  1. 定期检查 - 使用工具定期检查内存使用情况
  2. 代码审查 - 在代码审查中重点关注资源管理
  3. 单元测试 - 编写测试验证资源正确释放
  4. 监控告警 - 在生产环境设置内存使用告警

总结

多线程和并发编程是Java开发中的重要技能,掌握线程安全、锁机制、并发工具类、线程池等核心概念对于编写高性能、高并发的应用程序至关重要。

重点掌握: 线程安全机制、锁的使用和选择、并发工具类的应用、线程池的配置和优化、JMM内存模型、性能优化技巧

通过深入理解这些概念和原理,能够在面试中展现出扎实的多线程编程功底。

http://www.dtcms.com/a/463566.html

相关文章:

  • 网站版面结构广州企业如何建网站
  • 化妆品公司网站源码网页制作软件大全
  • 用win2003做网站本科专业 网站开发
  • 网站ome系统怎么做装修公司网站怎么做的
  • 婚纱摄影网站优化技巧php wordpress 代码模板
  • 公司网站维护价格表2023网站关键词堆砌
  • 咸阳网站建设xymokj南宁庄关键词推广优化方案
  • 如何做网站系统海口网站建设q479185700棒
  • 德州北京网站建设seo优化大公司排名
  • html5网站动态效果软件工程就业方向和前景
  • 个人网站模板 html5合肥营销网站建设公司
  • 多种大连网站建设如何更换wordpress域名
  • 建设银行北京分行招聘网站php做网站安全
  • cms 企业网站技能培训有哪些科目
  • 绍兴市工程建设网站wordpress过去指定分类文章
  • 如何把jQuery特效做网站背景用php做的旅游网站
  • 商标设计网站是哪个网站排名软件包年
  • 郑州市建设厅官方网站深圳品牌策划公司
  • 爱站小工具苏州专业网站建设
  • 徐州开发的网站企业建设网站的步骤是什么?
  • 网站开发成本预算表盐城做网站的价格
  • 政务公开 加强门户网站建设wordpress 走马灯
  • 广州建设网站 公司网站设计要求 优帮云
  • 南宁建设信息网站济南网站建设排名
  • 蓝田微网站建设站酷网官网
  • 快速建站套餐上海网络广告公司
  • 无锡做网站选优易信做网站要什么语言
  • 网站怎么续费wordpress添加字体
  • 丹东网站推广自己公司怎样弄个网站
  • 网站建设推广总结做网站是否需要自购服务器