Java并发面试题300道(详解版)

内容纲要

Java并发面试题300道(详解版)

每道题包含:答案详解 + 代码示例 + 记忆要点


目录


基础篇(1-50)

Q1: 线程和进程的区别?

答案:

维度 进程 线程
定义 资源分配的基本单位 CPU调度的基本单位
内存 独立的内存空间 共享进程的内存空间
通信 需要IPC(管道、共享内存等) 直接共享变量,方便通信
切换开销 大(需要保存上下文、TLB等) 小(只需保存寄存器、栈指针)
创建销毁
独立性 进程间独立 依赖父进程

记忆要点:

  • 进程 = 资源容器(独立的地址空间)
  • 线程 = 执行单位(共享进程资源)
  • 一句话:进程是操作系统分配资源的最小单位,线程是CPU调度的最小单位

Q2: 线程的5种状态及转换?

答案:

     NEW (新建)
       │
       │ start()
       ▼
   RUNNABLE (可运行)
    /   │   \
   │    │    │
   │    │    │ yield()/时间片用完
   │    │    ▼
   │    │  RUNNABLE
   │    │
   │    │ 获取锁失败
   │    │    │
   │    │    ▼
   │    │ BLOCKED (阻塞)
   │    │    │
   │    │ 获得锁
   │    │    │
   │    │    │
   │    │    ▼
   │    │  RUNNABLE
   │    │
   │    │ wait()/join()/park()
   │    │    │
   │    │    ▼
   │    │ WAITING (等待)
   │    │    │
   │    │ notify()/unpark()
   │    │    │
   │    │    ▼
   │    │  RUNNABLE
   │    │
   │    │ wait(1000)/sleep(1000)
   │    │    │
   │    │    ▼
   │    │ TIMED_WAITING (计时等待)
   │    │    │
   │    │ 超时/notify()
   │    │    │
   │    │    ▼
   │    │  RUNNABLE
   │    │
   │    │ 执行完毕/异常
   │    │    │
   │    │    ▼
   │    │ TERMINATED (终止)

代码示例:

public class ThreadState {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(1000);  // TIMED_WAITING
                synchronized (ThreadState.class) {
                    ThreadState.class.wait();  // WAITING
                }
            } catch (Exception e) {}
        });

        System.out.println("NEW: " + thread.getState());  // NEW
        thread.start();
        System.out.println("RUNNABLE: " + thread.getState());  // RUNNABLE

        Thread.sleep(500);
        System.out.println("TIMED_WAITING: " + thread.getState());  // TIMED_WAITING

        synchronized (ThreadState.class) {
            ThreadState.class.notifyAll();
        }

        Thread.sleep(100);
        System.out.println("TERMINATED: " + thread.getState());  // TERMINATED
    }
}

记忆要点:

  • 5种状态:NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED
  • RUNNABLE = 就绪 + 运行
  • BLOCKED = 等锁
  • WAITING = 无限期等待(wait/join)
  • TIMED_WAITING = 限时等待(sleep/wait带时间)

Q3: 如何创建线程?有哪些方式?

答案:

// 方式1: 继承Thread类(不推荐)
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("继承Thread");
    }
}
new MyThread().start();

// 方式2: 实现Runnable接口(推荐)
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("实现Runnable");
    }
}
new Thread(new MyRunnable()).start();

// 方式3: Lambda表达式(推荐)
new Thread(() -> {
    System.out.println("Lambda创建线程");
}).start();

// 方式4: 实现Callable接口(可获取返回值)
Callable<String> callable = () -> {
    return "Callable返回值";
};
FutureTask<String> future = new FutureTask<>(callable);
new Thread(future).start();
String result = future.get();  // 阻塞获取结果

// 方式5: 线程池(推荐)
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> System.out.println("线程池"));

// 方式6: CompletableFuture(推荐)
CompletableFuture.runAsync(() -> System.out.println("异步任务"));

记忆要点:

  • 继承Thread:单继承限制,不推荐
  • 实现Runnable:灵活,推荐
  • 实现Callable:可返回值、抛异常
  • 线程池:生产环境推荐
  • 核心原则:优先使用线程池,避免手动创建线程

Q4: start()和run()的区别?

答案:

特性 start() run()
功能 启动新线程,调用run() 普通方法调用,不创建新线程
线程 在新线程中执行 在当前线程中执行
调用次数 只能调用一次 可多次调用
并发 真正的并发 串行执行

代码示例:

public class StartVsRun {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("当前线程: " + Thread.currentThread().getName());
        });

        System.out.println("=== 调用start() ===");
        thread.start();  // 输出: Thread-0(新线程)

        System.out.println("=== 调用run() ===");
        thread.run();    // 输出: main(当前线程)
    }
}

start()源码核心逻辑:

// Thread.start() 简化版
public synchronized void start() {
    if (threadStatus != 0)  // 检查状态
        throw new IllegalThreadStateException();
    start0();  // native方法,创建线程并调用run()
}
private native void start0();  // JVM底层实现

记忆要点:

  • start() = 启动新线程 → 创建线程 → 调用run()
  • run() = 普通方法,在当前线程执行
  • 常见错误:调用run()而不是start(),导致没有真正并发

Q5: sleep()和wait()的区别?

答案:

特性 sleep() wait()
所属类 Thread Object
锁持有 不释放锁 释放锁
唤醒方式 超时自动唤醒 需要notify/notifyAll或超时
使用位置 任意位置 必须在synchronized块中
异常 InterruptedException InterruptedException + IllegalMonitorStateException

代码示例:

public class SleepVsWait {
    private static final Object lock = new Object();

    public static void main(String[] args) throws Exception {
        // sleep()示例 - 不释放锁
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread-1: 获得锁");
                try {
                    Thread.sleep(3000);  // 睡眠但持有锁
                } catch (InterruptedException e) {}
                System.out.println("Thread-1: 释放锁");
            }
        }).start();

        Thread.sleep(100);

        new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread-2: 获得锁");
            }
        }).start();

        // ========================================
        // wait()示例 - 释放锁
        synchronized (lock) {
            System.out.println("Main: 获得锁");
            new Thread(() -> {
                synchronized (lock) {
                    System.out.println("Thread: 获得锁并通知");
                    lock.notify();
                }
            }).start();
            Thread.sleep(100);
            System.out.println("Main: 释放锁并等待");
            lock.wait();  // 释放锁,等待唤醒
            System.out.println("Main: 被唤醒,重新获得锁");
        }
    }
}

wait()源码核心:

// Object.wait() 简化逻辑
public final void wait() throws InterruptedException {
    if (!Thread.currentThread().holdsLock(this))
        throw new IllegalMonitorStateException();  // 必须持有锁
    wait(0);  // 调用native方法,释放锁并等待
}

记忆要点:

  • sleep() = 休眠不释放锁(CPU给其他线程,但锁还在手里)
  • wait() = 等待释放锁(CPU+锁都交出去)
  • wait()必须在synchronized中,否则IllegalMonitorStateException
  • 面试重点:wait会释放锁,sleep不会

Q6: notify()和notifyAll()的区别?

答案:

特性 notify() notifyAll()
唤醒数量 唤醒一个等待线程 唤醒所有等待线程
选择策略 JVM选择(不确定是哪个) 全部唤醒
效率 高(只唤醒一个) 低(唤醒所有)
风险 可能死锁或假死 无死锁风险

代码示例:

public class NotifyVsNotifyAll {
    private static final Object lock = new Object();
    private static int conditionA = 0;
    private static int conditionB = 0;

    public static void main(String[] args) {
        // 等待条件A的线程
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                synchronized (lock) {
                    while (conditionA == 0) {
                        try { lock.wait(); } catch (Exception e) {}
                    }
                    System.out.println("条件A满足");
                }
            }).start();
        }

        // 等待条件B的线程
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                synchronized (lock) {
                    while (conditionB == 0) {
                        try { lock.wait(); } catch (Exception e) {}
                    }
                    System.out.println("条件B满足");
                }
            }).start();
        }

        // 使用notify()的问题
        new Thread(() -> {
            synchronized (lock) {
                conditionA = 1;
                lock.notify();  // 可能唤醒了等待B的线程!
                // 唤醒的线程检查conditionB不满足,继续wait
                // 真正等待A的线程永远无法被唤醒 → 假死
            }
        }).start();
    }
}

// 正确做法:使用notifyAll()
synchronized (lock) {
    conditionA = 1;
    lock.notifyAll();  // 唤醒所有,让每个线程自己检查条件
}

记忆要点:

  • notify() = 唤醒一个,但不确定是哪个
  • notifyAll() = 唤醒所有,各线程自己判断条件
  • 最佳实践:始终使用notifyAll(),避免信号丢失
  • 特殊情况可以用notify():只有一个等待线程时

Q7: wait()为什么要在synchronized块中?

答案:

原因1:防止竞争条件

// 如果wait()不需要synchronized,会发生什么?
public class RaceCondition {
    private boolean flag = false;

    // 线程1
    public void waitMethod() {
        if (!flag) {        // ← 检查
            wait();         // ← 等待 (如果中间flag被修改怎么办?)
        }
    }

    // 线程2
    public void notifyMethod() {
        flag = true;        // ← 修改
        notify();           // ← 通知
    }
}
// 问题:检查和等待之间不是原子的,可能漏掉通知

原因2:避免lost wake-up问题

// 错误示例:没有synchronized
public class LostWakeUp {
    private boolean ready = false;

    public void wait() throws InterruptedException {
        if (!ready) {
            this.wait();  // 假设可以
        }
    }

    public void notify() {
        ready = true;
        this.notify();     // 先notify了!
    }
}
// 如果notify()发生在wait()之前,通知就丢失了

原因3:源码强制检查

// Object.wait() 的实现
public final void wait() throws InterruptedException {
    // 检查当前线程是否持有对象的监视器锁
    if (!Thread.currentThread().holdsLock(this)) {
        throw new IllegalMonitorStateException();
    }
    wait0();  // native方法
}

正确做法:

synchronized (lock) {
    while (!condition) {  // while循环防止虚假唤醒
        lock.wait();      // 释放锁,等待唤醒
    }
    // 执行业务逻辑
}

记忆要点:

  • wait/notify必须在synchronized中
  • 防止竞争条件:检查和等待必须是原子的
  • 防止lost wake-up:先notify后wait会导致通知丢失
  • 必须用while循环检查条件,防止虚假唤醒

Q8: 为什么wait()会释放锁而sleep()不会?

答案:

设计理念不同:

方法 设计目的 是否应该释放锁
wait() 等待条件满足 必须释放,否则其他线程无法改变条件
sleep() 暂停执行 不应释放,仅仅休息一下

wait()释放锁的原因:

public class ProducerConsumer {
    private final Queue<String> queue = new LinkedList<>();
    private final int maxSize = 10;
    private final Object lock = new Object();

    // 消费者:等待队列非空
    public String consume() throws InterruptedException {
        synchronized (lock) {
            while (queue.isEmpty()) {
                lock.wait();  // ← 必须释放锁!
                // 否则生产者永远无法put,死锁
            }
            return queue.poll();
        }
    }

    // 生产者:等待队列不满
    public void produce(String item) throws InterruptedException {
        synchronized (lock) {
            while (queue.size() >= maxSize) {
                lock.wait();  // ← 必须释放锁!
                // 否则消费者永远无法poll,死锁
            }
            queue.offer(item);
            lock.notifyAll();
        }
    }
}

如果wait()不释放锁:

  1. 消费者检查queue.isEmpty() = true
  2. 消费者调用wait(),但持有锁
  3. 生产者想put,但被消费者持有的锁阻塞
  4. 死锁!

sleep()不释放锁的原因:

public class SleepWithLock {
    private final Object lock = new Object();
    private int counter = 0;

    public void increment() {
        synchronized (lock) {
            try {
                Thread.sleep(100);  // 仅仅休息一下
                // 休息期间不需要其他线程访问counter
                counter++;
            } catch (InterruptedException e) {}
        }
    }
}

JVM实现层面:

// HotSpot JVM 简化逻辑
// Object.wait()
void ObjectMonitor::wait() {
    // 1. 释放当前线程持有的锁
    release_lock();

    // 2. 把当前线程放入等待队列
    enqueue(waiter);

    // 3. 阻塞当前线程
    block_current_thread();

    // 4. 被唤醒后,重新获取锁
    acquire_lock();
}

// Thread.sleep()
void os::sleep() {
    // 1. 只是把线程从就绪队列移除
    remove_from_ready_queue();

    // 2. 让出CPU时间片
    yield_cpu();

    // 3. 不涉及锁操作
}

记忆要点:

  • wait() = 等待条件,必须释放锁让其他线程改变条件
  • sleep() = 休息一下,不涉及等待条件,不应释放锁
  • 面试标准答案:wait()需要其他线程修改等待条件,所以必须释放锁

Q9: 如何停止一个线程?

答案:

方法1: 使用volatile标志(推荐)

public class VolatileStop {
    private volatile boolean running = true;

    public void start() {
        new Thread(() -> {
            while (running) {  // 检查标志
                doWork();
            }
        }).start();
    }

    public void stop() {
        running = false;  // 设置标志
    }
}

方法2: 使用interrupt()(推荐)

public class InterruptStop {
    public void start() {
        Thread thread = new Thread() {
            @Override
            public void run() {
                while (!isInterrupted()) {  // 检查中断标志
                    doWork();
                }
                // 或捕获InterruptedException
                try {
                    while (true) {
                        Thread.sleep(100);
                        doWork();
                    }
                } catch (InterruptedException e) {
                    // 收到中断信号,退出
                    Thread.currentThread().interrupt();  // 恢复中断状态
                }
            }
        };
        thread.start();

        // 停止线程
        thread.interrupt();  // 设置中断标志
    }
}

方法3: 使用ExecutorService(推荐)

public class ExecutorStop {
    private ExecutorService executor = Executors.newFixedThreadPool(10);

    public void stop() {
        // 方式1: shutdown() - 平滑关闭
        executor.shutdown();  // 不再接受新任务,等待已提交任务完成

        // 方式2: shutdownNow() - 立即关闭
        List<Runnable> unfinished = executor.shutdownNow();  // 尝试停止所有任务

        // 等待关闭
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow();  // 强制关闭
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
        }
    }
}

❌ 已废弃的方法:stop(), suspend(), resume()

// 不要使用!这些方法已被废弃
thread.stop();      // 不安全,可能造成数据不一致
thread.suspend();   // 可能导致死锁
thread.resume();    // 配合suspend使用,不安全

为什么stop()不安全?

public class UnsafeStop {
    private static int counter = 0;

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter++;  // 不是原子操作!
            }
            System.out.println("完成,counter = " + counter);
        });
        thread.start();
        Thread.sleep(1);
        thread.stop();  // 立即停止!counter可能处于中间状态
        System.out.println("主线程,counter = " + counter);
        // 可能输出: counter = 123(不完整的结果)
    }
}

最佳实践总结:

public class BestPracticeStop {
    private volatile boolean running = true;
    private ExecutorService executor;
    private Future<?> future;

    public void start() {
        executor = Executors.newSingleThreadExecutor();
        future = executor.submit(() -> {
            try {
                while (running && !Thread.currentThread().isInterrupted()) {
                    doWork();
                }
            } catch (InterruptedException e) {
                // 被中断,正常退出
            }
        });
    }

    public void stop() {
        running = false;           // 1. 设置标志
        if (future != null) {
            future.cancel(true);    // 2. 取消任务
        }
        if (executor != null) {
            executor.shutdownNow(); // 3. 关闭线程池
        }
    }
}

记忆要点:

  • 不要使用stop()、suspend()、resume()(已废弃,不安全)
  • 推荐方法:interrupt()volatile标志
  • 使用ExecutorService时,用shutdownNow()
  • 线程应该定期检查中断标志或响应InterruptedException

Q10: interrupt()、isInterrupted()、interrupted()的区别?

答案:

方法 作用 静态 是否清除标志
interrupt() 设置中断标志 否(设置标志)
isInterrupted() 检查中断标志 否(不清除)
interrupted() 检查并清除标志 是(清除标志)

代码示例:

public class InterruptDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("线程开始");

            // 方式1: isInterrupted() - 不清除标志
            while (!Thread.currentThread().isInterrupted()) {
                // doWork();
            }
            System.out.println("检测到中断1");
            System.out.println("再次检查: " +
                Thread.currentThread().isInterrupted());  // true

            // 方式2: interrupted() - 清除标志
            boolean interrupted = Thread.interrupted();
            System.out.println("interrupted() = " + interrupted);  // true
            System.out.println("再次检查: " +
                Thread.currentThread().isInterrupted());  // false (已清除)
        });

        thread.start();
        thread.interrupt();  // 设置中断标志
    }
}

interrupt()的工作机制:

// Thread.interrupt() 简化实现
public void interrupt() {
    // 1. 设置中断标志
    interrupted = true;

    // 2. 如果线程在sleep/wait/join等阻塞状态
    //    会收到InterruptedException,并清除中断标志
    if (threadState == BLOCKED || threadState == WAITING) {
        // 唤醒线程,抛出InterruptedException
        throw new InterruptedException();
    }
}

各种场景下的响应:

// 场景1: 正常运行 - 需要主动检查
Thread thread = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 业务逻辑
    }
});

// 场景2: 阻塞在sleep() - 抛出异常
Thread thread = new Thread(() -> {
    try {
        Thread.sleep(1000);  // 被interrupt时会抛InterruptedException
    } catch (InterruptedException e) {
        // 处理中断
    }
});

// 场景3: 阻塞在wait() - 抛出异常
Thread thread = new Thread(() -> {
    synchronized (lock) {
        try {
            lock.wait();  // 被interrupt时会抛InterruptedException
        } catch (InterruptedException e) {}
    }
});

// 场景4: 阻塞在I/O - 抛出ClosedByInterruptException
Thread thread = new Thread(() -> {
    try {
        SocketChannel channel = SocketChannel.open(...);
        channel.configureBlocking(true);
        channel.read(buffer);  // 被interrupt时会抛ClosedByInterruptException
    } catch (ClosedByInterruptException e) {
        // 处理中断
    }
});

// 场景5: 阻塞在Selector.select() - 立即返回
Thread thread = new Thread(() -> {
    try {
        Selector selector = Selector.open();
        selector.select();  // 被interrupt时立即返回
    } catch (ClosedSelectorException e) {}
});

最佳实践:

public class BestPracticeInterrupt {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    doWork();
                }
            } catch (InterruptedException e) {
                // 重要:捕获异常后,通常需要退出线程
                // 可以执行清理工作
                cleanup();
            } finally {
                // 恢复中断状态(如果需要通知调用者)
                Thread.currentThread().interrupt();
            }
        });
    }
}

记忆要点:

  • interrupt() = 设置中断标志,唤醒阻塞线程
  • isInterrupted() = 检查标志(不清除)
  • interrupted() = 检查并清除标志(静态方法)
  • 线程应该定期检查中断标志或响应InterruptedException

Q11: 什么是守护线程?

答案:

定义:
守护线程(Daemon Thread)是优先级较低的线程,用于服务用户线程。当所有用户线程结束时,JVM会退出,不管守护线程是否执行完毕。

特点:

特性 说明
生命周期 依赖于用户线程,用户线程结束则守护线程终止
优先级 通常较低,但不绝对
用途 后台任务:GC、监控、定时清理等
JVM退出 只要有用户线程存活,JVM不退出

代码示例:

public class DaemonThreadDemo {
    public static void main(String[] args) throws Exception {
        Thread daemon = new Thread(() -> {
            int i = 0;
            while (true) {
                System.out.println("守护线程: " + i++);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {}
            }
        });
        daemon.setDaemon(true);  // 设置为守护线程
        daemon.start();

        System.out.println("主线程休眠2秒");
        Thread.sleep(2000);
        System.out.println("主线程结束,JVM退出");
        // 守护线程也会被强制终止
    }
}

对比:用户线程 vs 守护线程

public class UserVsDaemon {
    public static void main(String[] args) throws Exception {
        // 守护线程
        Thread daemon = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("守护线程: " + i);
                try { Thread.sleep(500); } catch (Exception e) {}
            }
            System.out.println("守护线程完成");
        });
        daemon.setDaemon(true);

        // 用户线程
        Thread user = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("用户线程: " + i);
                try { Thread.sleep(500); } catch (Exception e) {}
            }
            System.out.println("用户线程完成");
        });

        daemon.start();
        user.start();

        user.join();  // 等待用户线程结束
        System.out.println("主线程结束");

        // JVM退出,守护线程可能没执行完
    }
}

常见守护线程:

  • FinalizerThread - 执行对象的finalize()方法
  • ReferenceHandler - 处理软/弱/虚引用
  • JVM内置的垃圾回收线程

设置限制:

public class DaemonLimit {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("线程执行");
        });

        // ✅ 正确:启动前设置
        thread.setDaemon(true);
        thread.start();

        // ❌ 错误:启动后设置
        Thread thread2 = new Thread(() -> {});
        thread2.start();
        thread2.setDaemon(true);  // 抛出IllegalThreadStateException
    }
}

内存泄漏风险:

public class DaemonMemoryLeak {
    private static final Map<Integer, Object> cache = new HashMap<>();

    public static void main(String[] args) {
        Thread daemon = new Thread(() -> {
            while (true) {
                for (int i = 0; i < 10000; i++) {
                    cache.put(i, new byte[1024]);  // 持续分配内存
                }
                try { Thread.sleep(100); } catch (Exception e) {}
            }
        });
        daemon.setDaemon(true);
        daemon.start();

        // 主线程结束,但JVM还没退出(等待GC)
        // 守护线程仍在分配内存,可能导致OOM
    }
}

记忆要点:

  • 守护线程 = 后台服务线程,用户线程结束则终止
  • GC线程、Finalizer线程都是守护线程
  • 必须在start()前设置setDaemon(true)
  • 不要在守护线程中持有重要资源或执行关键任务

Q12: ThreadLocal的作用?

答案:

定义:
ThreadLocal提供线程局部变量,每个线程都有独立的副本,互不影响。

核心作用:

作用 场景 示例
线程隔离 避免线程安全问题 SimpleDateFormat、数据库连接
上下文传递 链路追踪 TraceId、UserId
资源持有 减少传参 数据库连接、Session

代码示例:

public class ThreadLocalDemo {

    // 示例1: 线程隔离 - SimpleDateFormat
    private static final ThreadLocal<SimpleDateFormat> dateFormat =
        ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

    public void formatDate() {
        // 每个线程有自己的SimpleDateFormat,线程安全
        String date = dateFormat.get().format(new Date());
        System.out.println(date);
    }

    // 示例2: 用户上下文传递
    private static final ThreadLocal<String> userContext = new ThreadLocal<>();

    public void processRequest(String userId) {
        userContext.set(userId);  // 设置当前用户
        try {
            serviceA();  // 所有方法都可以获取当前用户
            serviceB();
        } finally {
            userContext.remove();  // 重要:用完清理
        }
    }

    private void serviceA() {
        String userId = userContext.get();  // 获取当前用户
        System.out.println("Service A处理: " + userId);
    }

    // 示例3: 数据库连接管理
    private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();

    public Connection getConnection() {
        Connection conn = connectionHolder.get();
        if (conn == null) {
            conn = DriverManager.getConnection(url);
            connectionHolder.set(conn);
        }
        return conn;
    }

    // 示例4: 传递调用链路ID
    public class TraceIdContext {
        private static final ThreadLocal<String> traceId = new ThreadLocal<>();

        public static void setTraceId(String id) {
            traceId.set(id);
        }

        public static String getTraceId() {
            return traceId.get();
        }

        public static void clear() {
            traceId.remove();
        }
    }
}

工作原理:

Thread1 ─────────────────────────────┐
  ├─ ThreadLocalMap                  │
  │   ├─ ThreadLocal1 → Value1       │
  │   └─ ThreadLocal2 → Value2       │
  └─ ...                             │
                                    │
Thread2 ─────────────────────────────┤
  ├─ ThreadLocalMap                  │
  │   ├─ ThreadLocal1 → Value3       │  每个线程有自己的Map
  │   └─ ThreadLocal2 → Value4       │  值互不影响
  └─ ...                             │
                                    │
ThreadLocal ────────────────────────┘
  (作为ThreadLocalMap的key)

ThreadLocal源码核心:

// Thread.java
public class Thread implements Runnable {
    // 每个线程持有一个ThreadLocalMap
    ThreadLocal.ThreadLocalMap threadLocals;
}

// ThreadLocal.java
public class ThreadLocal<T> {
    public T get() {
        Thread t = Thread.currentThread();      // 获取当前线程
        ThreadLocalMap map = t.threadLocals;    // 获取线程的Map
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);  // this作为key
            if (e != null) {
                return (T) e.value;
            }
        }
        return setInitialValue();  // 初始化
    }

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = t.threadLocals;
        if (map != null) {
            map.set(this, value);  // this作为key
        } else {
            createMap(t, value);
        }
    }

    public void remove() {
        ThreadLocalMap map = Thread.currentThread().threadLocals;
        if (map != null) {
            map.remove(this);
        }
    }
}

InheritableThreadLocal(可继承的ThreadLocal):

public class InheritableThreadLocalDemo {
    private static final InheritableThreadLocal<String> inheritableLocal =
        new InheritableThreadLocal<>();

    public static void main(String[] args) {
        inheritableLocal.set("父线程的值");

        new Thread(() -> {
            // 子线程可以继承父线程的ThreadLocal值
            System.out.println("子线程: " + inheritableLocal.get());
        }).start();
    }
}

记忆要点:

  • ThreadLocal = 每个线程独立的变量副本
  • 原理:Thread持有ThreadLocalMap,ThreadLocal作为key
  • 用途:线程隔离、上下文传递、资源持有
  • 重要:使用完要remove(),防止内存泄漏

Q13: ThreadLocal的内存泄漏问题?

答案:

问题根源:

Thread ───→ ThreadLocalMap ───→ Entry ───→ ThreadLocal (WeakReference)
                                              ↓
                                            Value (强引用)

问题:
- Entry的key(ThreadLocal)是弱引用,ThreadLocal被回收后key为null
- 但Entry的value是强引用,无法被GC回收
- 线程一直存活(如线程池),ThreadLocalMap也一直存在
- 导致value泄漏

代码示例:

public class ThreadLocalLeak {
    private static final ThreadLocal<byte[]> local = new ThreadLocal<>();
    private static final ExecutorService executor = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            executor.submit(() -> {
                // 分配大对象
                local.set(new byte[10 * 1024 * 1024]);  // 10MB

                // 业务逻辑
                doWork();

                // ❌ 忘记remove()!
                // local.remove();

                // 线程放回线程池,继续复用
                // ThreadLocalMap中的value(10MB)一直存在
                // 100个任务 = 1000MB内存泄漏
            });
        }
    }
}

解决方案:

// 方式1: 使用try-finally确保清理
public class CorrectUsage {
    private static final ThreadLocal<byte[]> local = new ThreadLocal<>();

    public void process() {
        local.set(new byte[10 * 1024 * 1024]);
        try {
            doWork();
        } finally {
            local.remove();  // ✅ 确保清理
        }
    }
}

// 方式2: Java 8+ 使用withInitial()并配合remove
private static final ThreadLocal<SimpleDateFormat> dateFormat =
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

// 方式3: 使用try-with-resources风格
public class AutoCleanThreadLocal<T> implements AutoCloseable {
    private final ThreadLocal<T> threadLocal;

    public AutoCleanThreadLocal(Supplier<T> supplier) {
        this.threadLocal = ThreadLocal.withInitial(supplier);
    }

    public T get() {
        return threadLocal.get();
    }

    public void set(T value) {
        threadLocal.set(value);
    }

    @Override
    public void close() {
        threadLocal.remove();
    }
}

// 使用
try (var local = new AutoCleanThreadLocal<>(() -> new Resource())) {
    local.get().doSomething();
}  // 自动清理

ThreadLocalMap的清理机制:

// ThreadLocalMap内部有清理逻辑
class ThreadLocalMap {
    private void expungeStaleEntry(int staleSlot) {
        // 清理key为null的Entry
        for (int i = staleSlot; ; i = nextIndex(i, len)) {
            Entry e = tab[i];
            if (e == null)
                break;
            if (e.get() == null)  // key被回收
                expungeStaleEntry(i);  // 清理entry
        }
    }

    // set()/get()/remove()时都会触发清理
    public T get(ThreadLocal<?> key) {
        Entry e = getEntry(key);
        if (e != null)
            return (T) e.value;

        // 没找到,触发清理
        return getAfterMiss(key);
    }
}

最佳实践:

public class ThreadLocalBestPractice {

    // 1. 基础用法:配合try-finally
    private static final ThreadLocal<Connection> connectionLocal = new ThreadLocal<>();

    public Connection getConnection() {
        Connection conn = connectionLocal.get();
        if (conn == null) {
            conn = createConnection();
            connectionLocal.set(conn);
        }
        return conn;
    }

    public void releaseConnection() {
        connectionLocal.remove();  // ✅ 手动清理
    }

    // 2. 封装成工具类
    public class ThreadLocalUtils {
        private static final ThreadLocal<Map<String, Object>> context =
            ThreadLocal.withInitial(HashMap::new);

        public static void put(String key, Object value) {
            context.get().put(key, value);
        }

        public static <T> T get(String key) {
            return (T) context.get().get(key);
        }

        public static void clear() {
            context.remove();  // ✅ 统一清理入口
        }
    }

    // 3. 在拦截器和过滤器中使用
    public class RequestFilter implements Filter {
        @Override
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
            try {
                ThreadLocalUtils.put("requestId", generateId());
                chain.doFilter(req, res);
            } finally {
                ThreadLocalUtils.clear();  // ✅ 请求结束清理
            }
        }
    }
}

内存泄漏检测:

// 检测ThreadLocal内存泄漏
public class ThreadLocalLeakDetector {
    public static void detect() {
        Thread[] threads = getAllThreads();
        for (Thread thread : threads) {
            try {
                Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
                threadLocalsField.setAccessible(true);
                Object threadLocalMap = threadLocalsField.get(thread);

                if (threadLocalMap != null) {
                    Field tableField = threadLocalMap.getClass().getDeclaredField("table");
                    tableField.setAccessible(true);
                    Object[] table = (Object[]) tableField.get(threadLocalMap);

                    int leakCount = 0;
                    for (Object entry : table) {
                        if (entry != null) {
                            Field valueField = entry.getClass().getDeclaredField("value");
                            valueField.setAccessible(true);
                            Object value = valueField.get(entry);

                            Field keyField = entry.getClass().getDeclaredField("referent");
                            keyField.setAccessible(true);
                            Object key = keyField.get(entry);

                            if (key == null && value != null) {
                                leakCount++;
                            }
                        }
                    }
                    if (leakCount > 0) {
                        System.out.println("线程 " + thread.getName() + " 有 " + leakCount + " 个泄漏的Entry");
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

记忆要点:

  • ThreadLocalMap的Entry.key是弱引用,value是强引用
  • 线程池场景下,线程不销毁,value可能泄漏
  • 必须在使用完调用remove()清理
  • 配合try-finally确保清理
  • get()/set()会触发部分清理,但不可靠

Q14: ThreadLocalMap的数据结构?

答案:

ThreadLocalMap结构:

Thread ───→ threadLocals (ThreadLocalMap)
               │
               ▼
        ThreadLocalMap {
            Entry[] table;  // 开放寻址哈希表
            int size;       // 当前大小
            int threshold;  // 扩容阈值
        }

Entry {
    ThreadLocal<?> key (WeakReference);  // 弱引用
    Object value;                         // 强引用
}

为什么使用开放寻址而不是链表?

特性 开放寻址 链表法
内存占用 连续内存,占用少 需要额外Node对象
CPU缓存 更友好 较差
冲突处理 探测下一个位置 链表
删除 标记删除 真正删除
适用场景 空间敏感,冲突少 冲突多,动态扩容

源码分析:

static class ThreadLocalMap {
    // 初始容量16,必须是2的幂
    private static final int INITIAL_CAPACITY = 16;

    // Entry数组
    private Entry[] table;

    // Entry继承WeakReference
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);  // key是弱引用
            value = v;
        }
    }

    // 哈希计算:ThreadLocal的hashCode
    private int hash(ThreadLocal<?> key) {
        return key.hashCode() & (table.length - 1);  // 与运算
    }

    // 设置值
    private void set(ThreadLocal<?> key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
        int i = key.threadLocalHashCode & (len - 1);  // 哈希位置

        // 开放寻址:线性探测
        for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            if (k == key) {  // key相同,覆盖
                e.value = value;
                return;
            }
            if (k == null) {  // key被回收,替换
                replaceStaleEntry(key, value, i);
                return;
            }
        }

        // 没找到,创建新Entry
        tab[i] = new Entry(key, value);
        size++;
        if (size >= threshold) {  // 扩容
            rehash();
        }
    }

    // 获取值
    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];

        // 线性探测
        if (e != null && e.get() == key) {
            return e;  // 直接命中
        }

        // 没命中,继续探测
        if (e != null) {
            Entry e = tab[i = nextIndex(i, len)];
            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    return getEntryAfterMiss(key, i, e);
                e = tab[i = nextIndex(i, len)];
            }
        }
        return null;  // 没找到
    }

    // 删除
    private void remove(ThreadLocal<?> key) {
        Entry[] tab = table;
        int len = tab.length;
        int i = key.threadLocalHashCode & (len - 1);

        for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
            if (e.get() == key) {
                e.clear();      // 清除key引用
                e.value = null; // 清除value引用
                size--;
                expungeStaleEntry(i);  // 清理并探测
                return;
            }
        }
    }

    // 清理并重新探测(开放寻址的核心)
    private int expungeStaleEntry(int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;

        // 清除当前位置
        tab[staleSlot].value = null;
        tab[staleSlot] = null;
        size--;

        // 重新探测后续元素(因为开放寻址,可能需要前移)
        Entry e;
        int i;
        for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();
            if (k == null) {
                e.value = null;
                tab[i] = null;
                size--;
            } else {
                int h = k.threadLocalHashCode & (len - 1);
                if (h != i) {  // 位置不对,需要前移
                    tab[i] = null;
                    while (tab[h] != null) {
                        h = nextIndex(h, len);
                    }
                    tab[h] = e;
                }
            }
        }
        return i;
    }
}

为什么初始容量是16?

1. 2的幂次方:哈希计算可以用位运算代替取模
   hash & (length - 1) == hash % length

2. 不太大也不太小:
   - 太小:冲突多,探测次数多
   - 太大:内存浪费

3. 扩容阈值 = 2/3容量:
   - 负载因子2/3,平衡空间和性能
   - 大于HashMap的0.75,因为开放寻址需要更多空间

冲突处理示例:

假设容量=4,哈希值:
- ThreadLocal1: hashCode % 4 = 1
- ThreadLocal2: hashCode % 4 = 1 (冲突)
- ThreadLocal3: hashCode % 4 = 2

初始状态:
[null, null, null, null]

插入ThreadLocal1:
[null, Entry1, null, null]

插入ThreadLocal2 (位置1冲突,线性探测到2):
[null, Entry1, Entry2, null]

插入ThreadLocal3 (位置2冲突,线性探测到3):
[null, Entry1, Entry2, Entry3]

删除Entry1 (位置1):
[null, null, Entry2, Entry3]

重要:Entry2的原始哈希位置应该是1!
      需要前移:
[null, Entry2, Entry3, null]

记忆要点:

  • ThreadLocalMap = 开放寻址哈希表
  • Entry.key = WeakReference,Entry.value = 强引用
  • 初始容量16,扩容阈值2/3
  • 开放寻址:冲突时线性探测下一个位置
  • 删除后需要重新探测,因为开放寻址可能需要前移元素

Q15: InheritableThreadLocal是什么?

答案:

定义:
InheritableThreadLocal是ThreadLocal的子类,可以让子线程继承父线程的ThreadLocal值。

对比:

特性 ThreadLocal InheritableThreadLocal
继承性 子线程不继承父线程的值 子线程继承父线程的值
使用场景 线程隔离 父子线程传递上下文
性能 略快 略慢(需要复制)

代码示例:

public class InheritableThreadLocalDemo {

    // 普通ThreadLocal:子线程无法继承
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    // InheritableThreadLocal:子线程可以继承
    private static final InheritableThreadLocal<String> inheritableLocal =
        new InheritableThreadLocal<>();

    public static void main(String[] args) {
        threadLocal.set("主线程-ThreadLocal");
        inheritableLocal.set("主线程-Inheritable");

        new Thread(() -> {
            System.out.println("子线程 ThreadLocal: " + threadLocal.get());
            // 输出: null (无法继承)

            System.out.println("子线程 Inheritable: " + inheritableLocal.get());
            // 输出: 主线程-Inheritable (继承成功)
        }).start();
    }
}

工作原理:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    // 覆盖getMap(),使用inheritableThreadLocals
    ThreadLocalMap getMap(Thread t) {
        return t.inheritableThreadLocals;  // 而不是t.threadLocals
    }

    // 覆盖createMap()
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

    // 子线程创建时,复制父线程的值
    // 在Thread.init()中调用
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return parentMap != null ? parentMap.childMapValue() : null;
    }
}

Thread源码中的初始化:

// Thread.java
public class Thread implements Runnable {
    // 普通ThreadLocal
    ThreadLocal.ThreadLocalMap threadLocals;

    // 可继承的ThreadLocal
    ThreadLocal.ThreadLocalMap inheritableThreadLocals;

    private void init(ThreadGroup g, Runnable target, String name, ...) {
        // ...

        // 获取父线程
        Thread parent = currentThread();

        // 复制父线程的inheritableThreadLocals
        if (parent.inheritableThreadLocals != null) {
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        }
        // threadLocals不复制,初始为null
    }
}

修改影响:

public class ModifyInheritance {
    private static final InheritableThreadLocal<String> local =
        new InheritableThreadLocal<>();

    public static void main(String[] args) throws Exception {
        local.set("初始值");

        Thread child = new Thread() {
            @Override
            public void run() {
                System.out.println("子线程初始: " + local.get());  // 初始值
                local.set("子线程修改");
                System.out.println("子线程修改后: " + local.get());  // 子线程修改
            }
        };

        child.start();
        child.join();

        System.out.println("主线程: " + local.get());  // 初始值 (互不影响)
    }
}

线程池场景的问题:

public class ThreadPoolInheritance {
    private static final InheritableThreadLocal<String> context =
        new InheritableThreadLocal<>();

    private static final ExecutorService executor = Executors.newFixedThreadPool(2);

    public static void main(String[] throws Exception {
        // 第一次请求
        context.set("请求1");
        executor.submit(() -> {
            System.out.println("任务1: " + context.get());  // 请求1
        });

        Thread.sleep(100);

        // 第二次请求
        context.set("请求2");
        executor.submit(() -> {
            // 问题:如果线程复用,可能拿到旧的值!
            System.out.println("任务2: " + context.get());  // 可能是请求1或请求2
        });
    }
}

解决方案:TransmittableThreadLocal(阿里开源)

// 需要依赖:com.alibaba:transmittable-thread-local

public class TransmittableDemo {
    // TransmittableThreadLocal支持线程池场景
    private static final TransmittableThreadLocal<String> context =
        new TransmittableThreadLocal<>();

    private static final ExecutorService executor =
        TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));

    public static void main(String[] args) {
        // 自动在任务提交时复制上下文
        context.set("请求1");
        executor.submit(() -> {
            System.out.println("任务: " + context.get());  // 确保是请求1
        });
    }
}

使用场景:

public class InheritableUseCase {

    // 场景1: 传递TraceId
    private static final InheritableThreadLocal<String> traceId =
        new InheritableThreadLocal<>();

    public void processRequest() {
        traceId.set(generateTraceId());
        new Thread(() -> {
            // 子线程自动获得TraceId
            log(traceId.get(), "处理逻辑");
        }).start();
    }

    // 场景2: 传递用户身份
    private static final InheritableThreadLocal<UserInfo> userInfo =
        new InheritableThreadLocal<>();

    public void asyncProcess(UserInfo user) {
        userInfo.set(user);
        executor.submit(() -> {
            // 异步任务获得用户信息
            checkPermission(userInfo.get());
        });
    }
}

记忆要点:

  • InheritableThreadLocal = 可被子线程继承的ThreadLocal
  • 原理:Thread初始化时复制父线程的inheritableThreadLocals
  • 线程池场景不适用(线程复用导致上下文混乱)
  • 解决方案:TransmittableThreadLocal(阿里TTL)
  • 用途:父子线程传递上下文(TraceId、UserId等)

Q16: 线程的优先级有什么用?

答案:

定义:
线程优先级(Priority)是给线程调度器的一个"建议",表示线程的相对重要性。

Java线程优先级:

优先级常量 说明
MIN_PRIORITY 1 最低优先级
NORM_PRIORITY 5 默认优先级
MAX_PRIORITY 10 最高优先级

代码示例:

public class ThreadPriority {
    public static void main(String[] args) {
        Thread minThread = new Thread(() -> task("低优先级"));
        minThread.setPriority(Thread.MIN_PRIORITY);  // 1

        Thread normThread = new Thread(() -> task("普通优先级"));
        normThread.setPriority(Thread.NORM_PRIORITY);  // 5

        Thread maxThread = new Thread(() -> task("高优先级"));
        maxThread.setPriority(Thread.MAX_PRIORITY);  // 10

        maxThread.start();
        normThread.start();
        minThread.start();
    }

    private static void task(String name) {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + ": " + i);
        }
    }
}

操作系统线程优先级映射:

OS Java优先级映射
Windows 1-7级映射到系统优先级
Linux nice值映射
Solaris 直接映射

源码映射(HotSpot):

// os.cpp
int os::java_to_os_priority[] = {
    // Java优先级 -> 操作系统优先级
    -99999,  //  0 (Reserved)
    0,       //  1 -> Priority Idle
    1,       //  2
    2,       //  3
    ...
    7,       // 10 -> Priority Critical
    -99999   // 11+ (Reserved)
};

重要限制:

public class PriorityLimitations {
    public static void main(String[] args) {
        Thread thread = new Thread();
        thread.setPriority(15);  // ✅ 不会报错,但会被限制
        System.out.println(thread.getPriority());  // 输出: 10 (被限制到MAX)

        thread.setPriority(0);   // ✅ 不会报错
        System.out.println(thread.getPriority());  // 输出: 1 (被限制到MIN)

        thread.setPriority(-1);  // ❌ 抛出IllegalArgumentException
        System.out.println(thread.getPriority());
    }
}

不可靠的原因:

// 问题1: 时间片轮转
// 即使高优先级,也可能被时间片调度器切换出去

// 问题2: 优先级反转
// 低优先级线程持有锁,高优先级线程等待
// 导致高优先级被低优先级阻塞

// 问题3: 平台差异
// Windows和Linux对优先级的处理不同
// 跨平台行为不一致

优先级反转示例:

public class PriorityInversion {
    private static final Object lock = new Object();

    public static void main(String[] throws Exception {
        // 低优先级线程持有锁
        Thread lowThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("低优先级获得锁");
                Thread.sleep(1000);  // 持有锁1秒
                System.out.println("低优先级释放锁");
            }
        });
        lowThread.setPriority(Thread.MIN_PRIORITY);

        // 中优先级线程占用CPU
        Thread midThread = new Thread(() -> {
            for (int i = 0; i < 1000000; i++) {
                Math.sqrt(i);  // 纯计算,占用CPU
            }
            System.out.println("中优先级完成");
        });
        midThread.setPriority(Thread.NORM_PRIORITY);

        // 高优先级线程等待锁
        Thread highThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("高优先级执行");
            }
        });
        highThread.setPriority(Thread.MAX_PRIORITY);

        lowThread.start();
        Thread.sleep(10);  // 让低优先级先获得锁
        midThread.start();
        highThread.start();

        // 结果:
        // 高优先级被低优先级阻塞
        // 中优先级抢占CPU,延迟低优先级释放锁
        // 高优先级线程延迟执行
    }
}

最佳实践:

public class PriorityBestPractice {

    // ❌ 不要依赖优先级保证执行顺序
    public static void bad() {
        Thread high = new Thread(() -> task());
        high.setPriority(Thread.MAX_PRIORITY);  // 不保证先执行

        Thread low = new Thread(() -> task());
        low.setPriority(Thread.MIN_PRIORITY);

        high.start();
        low.start();
    }

    // ✅ 使用join()保证顺序
    public static void good() throws Exception {
        Thread first = new Thread(() -> task());
        Thread second = new Thread(() -> task());

        first.start();
        first.join();  // 等待first完成
        second.start();  // second在first之后执行
    }

    // ✅ 使用锁保证同步
    private static final Object lock = new Object();

    public static void good2() {
        new Thread(() -> {
            synchronized (lock) {
                task();
            }
        }).start();

        new Thread(() -> {
            synchronized (lock) {
                task();
            }
        }).start();
    }

    // ✅ 适当的优先级场景:后台任务
    public static void background() {
        Thread background = new Thread(() -> {
            while (true) {
                cleanup();
                try { Thread.sleep(60000); } catch (Exception e) {}
            }
        });
        background.setPriority(Thread.MIN_PRIORITY);  // 后台清理任务
        background.setDaemon(true);
        background.start();
    }
}

记忆要点:

  • 优先级 = 调度建议,不保证执行顺序
  • 范围:1-10,默认5
  • 优先级反转:低优先级持有锁,高优先级等待
  • 平台依赖:不同OS实现不同
  • 不要依赖优先级实现同步或顺序控制
  • 合理用途:后台任务、监控任务

Q17: 什么是上下文切换?

答案:

定义:
上下文切换(Context Switch)是CPU从一个线程/进程切换到另一个线程/进程的过程,保存当前上下文,恢复目标上下文。

切换流程:

线程A运行
    │
    ▼ 时间片到期 / 阻塞 / 高优先级就绪
    │
    ├─ 保存线程A的上下文
    │  ├─ 程序计数器 (PC) → 指令位置
    │  ├─ 栈指针 (SP)      → 栈顶位置
    │  ├─ 寄存器状态
    │  └─ CPU状态
    │
    ├─ 切换到线程B
    │
    ├─ 恢复线程B的上下文
    │  ├─ 程序计数器 ← 线程B的PC
    │  ├─ 栈指针      ← 线程B的SP
    │  ├─ 寄存器状态  ← 线程B的寄存器
    │  └─ CPU状态     ← 线程B的CPU状态
    │
    ▼
线程B继续运行

时间消耗:

操作 耗时
线程上下文切换 纳秒级
进程上下文切换 微秒级

为什么进程切换更慢?

特性 线程切换 进程切换
地址空间 不切换 需要切换(TLB刷新)
缓存 可能部分失效 大部分失效
数据量 寄存器+栈 寄存器+栈+页表
耗时 纳秒级 微秒级

测量上下文切换开销:

public class ContextSwitchBenchmark {
    private static final int ITERATIONS = 100_000_000;

    public static void main(String[] args) throws Exception {
        // 基准:单线程
        long start =1;
        for (int i = 0; i < ITERATIONS; i++) {
            counter++;
        }
    } long singleThreadTime = System.nanoTime() - start;
    System.out.println("单线程: " + singleThreadTime / 1_000_000 + "ms");

    // 多线程:有上下文切换
    final AtomicLong multiCounter = new AtomicLong(0);
    final Object lock = new Object();

    start = System.nanoTime();
    for (int i = 0; i < ITERATIONS; i++) {
        synchronized (lock) {
            multiCounter.incrementAndGet();
        }
    }
    long multiThreadTime = System.nanoTime() - start;
    System.out.println("多线程: " + multiThreadTime / 1_000_000 + "ms");

    // 计算切换开销
    System.out.println("上下文切换开销: " +
        (multiThreadTime - singleThreadTime) + "ns");
}

Linux查看上下文切换:

# 查看整体上下文切换
vmstat 1

# 输出示例:
# procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
#  r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
#  1  0      0 845124 292928 672760    0    0     0     0   20   30  0  0 100  0  0
#                                                             ↑in=中断  ↑cs=上下文切换

# 查看特定进程的上下文切换
pidstat -w -p <pid> 1

# 输出示例:
# 08:30:00 AM   UID       PID   cswch/s nvcswch/s  Command
# 08:30:01 AM     0      1234    100.00     10.00  java
#                                             ↑自愿切换 ↑非自愿切换

减少上下文切换的方法:

// 方法1: 使用无锁编程
public class NoLockExample {
    private final AtomicInteger counter = new AtomicInteger(0);

    public void increment() {
        counter.incrementAndGet();  // CAS,不涉及锁,减少切换
    }
}

// 方法2: 减少锁的持有时间
public class ReduceLockTime {
    private final Lock lock = new ReentrantLock();

    public void process() {
        prepareData();  // 不需要锁的操作

        lock.lock();
        try {
            updateSharedData();  // 只在必要时加锁
        } finally {
            lock.unlock();
        }

        processResult();  // 不需要锁的操作
    }
}

// 方法3: 使用线程池
public class ThreadPoolExample {
    private final ExecutorService executor = Executors.newFixedThreadPool(10);

    public void handleRequests(List<Request> requests) {
        for (Request req : requests) {
            executor.submit(() -> process(req));  // 复用线程,减少创建
        }
    }
}

// 方法4: 批量处理
public class BatchProcessing {
    public void process(List<Item> items) {
        // ❌ 每次都加锁
        for (Item item : items) {
            synchronized (this) {
                processItem(item);
            }
        }

        // ✅ 批量加锁
        synchronized (this) {
            for (Item item : items) {
                processItem(item);
            }
        }
    }
}

// 方法5: 使用CAS + 自旋
public class SpinLock {
    private final AtomicBoolean locked = new AtomicBoolean(false);

    public void lock() {
        // 自旋等待,不阻塞,不触发上下文切换
        while (!locked.compareAndSet(false, true)) {
            Thread.yield();  // 让出CPU但不阻塞
        }
    }

    public void unlock() {
        locked.set(false);
    }
}

Java中的上下文切换触发条件:

public class SwitchTriggers {
    // 1. 时间片用完
    // 由操作系统调度器决定

    // 2. Thread.sleep()
    Thread.sleep(1000);  // 主动让出CPU

    // 3. wait()
    synchronized (lock) {
        lock.wait();  // 释放锁,等待
    }

    // 4. 锁竞争
    synchronized (lock) {
        // 竞争锁时,未获得锁的线程阻塞
    }

    // 5. I/O操作
    socket.read(buffer);  // 阻塞I/O

    // 6. 高优先级线程就绪
    // 低优先级线程被抢占
}

JVM参数优化:

# 减少上下文切换的JVM参数

# 1. 设置合理的线程数
-XX:ParallelGCThreads=8  # GC线程数
-XX:ConcGCThreads=2      # 并发GC线程数

# 2. 使用偏向锁,减少不必要的锁竞争
-XX:+UseBiasedLocking
-XX:BiasedLockingStartupDelay=0

# 3. 调整自旋次数
-XX:PreBlockSpin=10  # 自旋次数

# 4. 使用G1GC,减少STW
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200

记忆要点:

  • 上下文切换 = 保存当前线程状态 + 恢复目标线程状态
  • 线程切换: 纳秒级,进程切换: 微秒级
  • 触发条件: 时间片、阻塞、I/O、锁竞争
  • 减少方法: 无锁编程、减少锁时间、线程池、批量处理
  • 可用vmstat/pidstat监控上下文切换次数

Q17-50: 基础篇剩余题目(简略版)

由于篇幅限制,以下是剩余题目的简要答案:

Q18: 线程调度策略有哪些?

  • 抢占式调度(Java默认):优先级高的可以抢占
  • 时间片轮转:每个线程分配时间片
  • 协作式调度:线程主动让出

Q19: 什么是用户线程和内核线程?

  • 用户线程:完全由用户空间管理(M:N模型)
  • 内核线程:由操作系统内核管理(1:1模型)

Q20: Java线程和操作系统线程的关系?

  • 传统:1:1映射,Java线程 = OS线程
  • 虚拟线程(Java 21+):M:N映射,多个虚拟线程共享少量OS线程

Q21: 什么是绿色线程?

  • 早期Java实现的用户线程,已被废弃
  • 由JVM管理,不依赖OS线程

Q22: 什么是纤程(Fiber)?

  • 轻量级线程,由用户态调度
  • Java 21的虚拟线程类似纤程

Q23: Java 21的虚拟线程是什么?

// 虚拟线程示例
Thread.ofVirtual().start(() -> {
    System.out.println("虚拟线程");
});

// 与平台线程对比
Thread.ofPlatform().start(() -> {
    System.out.println("平台线程");
});

Q24-30: synchronized相关

详见"锁机制"章节

Q31-33: volatile相关

详见"锁机制"章节

Q34-50: 其他基础概念


锁机制(51-100)

Q51: CAS的实现原理?

答案:

CAS(Compare-And-Swap)定义:
CAS是一种原子操作,比较内存位置的值,如果与期望值相同,则更新为新值。

CAS操作流程:

CAS(addr, expected, new)

1. 读取addr的实际值: actual = *addr
2. 比较actual == expected
   ├─ true:  *addr = new, 返回成功
   └─ false: 不更新, 返回失败

JVM实现(HotSpot):

// unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint expected, jint x)) {
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint*) index_oop_from_field_offset_long(p, offset);

  // 调用C++原子操作
  return (jint)(Atomic::cmpxchg(x, addr, expected)) == expected;
} UNSAFE_END

// atomic.hpp
inline jint Atomic::cmpxchg(jint exchange_value, volatile jint* dest, jint compare_value) {
  #ifdef AMD64
    // x86_64使用lock cmpxchg指令
    return (jint)os::atomic_cmpxchg((long)exchange_value, (volatile long*)dest, (long)compare_value);
  #endif
}

// os_x86.cpp
long os::atomic_cmpxchg(long exchange_value, volatile long* dest, long compare_value) {
  // lock前缀保证原子性
  long res = __sync_val_compare_and_swap(dest, compare_value, exchange_value);
  return res;
}

CPU层面实现:

x86架构:
LOCK CMPXCHG [mem], reg

LOCK前缀作用:
1. 确保指令原子执行(多核同步)
2. 缓存一致性协议(MESI)
3. 禁止指令重排序

Java CAS示例:

public class CASDemo {
    private static final AtomicReference<String> ref = new AtomicReference<>("A");

    public static void main(String[] args) {
        // CAS操作
        boolean success = ref.compareAndSet("A", "B");
        System.out.println("成功: " + success);  // true
        System.out.println("值: " + ref.get());    // B

        // 失败:期望值不匹配
        success = ref.compareAndSet("A", "C");
        System.out.println("成功: " + success);  // false
        System.out.println("值: " + ref.get());    // B (仍然是B)
    }
}

自旋锁实现:

public class SpinLock {
    private final AtomicBoolean locked = new AtomicBoolean(false);

    public void lock() {
        // CAS自旋
        while (!locked.compareAndSet(false, true)) {
            Thread.yield();  // 让出CPU
            // 或 LockSupport.parkNanos(1000);  // 短暂park
        }
    }

    public void unlock() {
        locked.set(false);
    }

    // 使用
    public void criticalSection() {
        lock();
        try {
            // 临界区
        } finally {
            unlock();
        }
    }
}

CAS的问题:

问题 说明 解决方案
ABA问题 A→B→A,CAS认为没变化 加版本号(AtomicStampedReference)
CPU开销 自旋占用CPU 限制自旋次数或用synchronized
只能更新一个变量 复合操作不原子 Atomic类或锁

记忆要点:

  • CAS = 比较并交换,原子操作
  • CPU层面:lock cmpxchg指令
  • 硬件保证原子性 + 缓存一致性
  • 问题:ABA、自旋开销、只能单变量
  • 解决:版本号、限制自旋、用锁

Q52-60: CAS、ABA、Atomic类相关

Q52: 什么是ABA问题?

答案:

初始状态: ref = A

线程1: 读取 ref = A
线程2: 读取 ref = A

线程2: CAS(A, B) → 成功, ref = B
线程2: CAS(B, A) → 成功, ref = A

线程1: CAS(A, C) → 成功!(虽然中间被改过)

问题:线程1认为值一直是A,实际经过了A→B→A

解决方案:

// 方案1: AtomicStampedReference
AtomicStampedReference<String> ref =
    new AtomicStampedReference<>("A", 0);

int stamp = ref.getStamp();  // 获取版本号
boolean success = ref.compareAndSet("A", "B", stamp, stamp + 1);
                                                    ↑      ↑
                                                旧值   新版本号

// 方案2: AtomicMarkableReference
AtomicMarkableReference<String> ref =
    new AtomicMarkableReference<>("A", false);

boolean success = ref.compareAndSet("A", "B", false, true);
                                                    ↑     ↑
                                                旧标记 新标记

记忆要点:

  • ABA = 值经过变化又回到原样
  • CAS无法检测ABA,可能错误更新成功
  • 解决:加版本号或标记(AtomicStampedReference)

Q53: 如何解决ABA问题?

答案:

见Q52


Q54: compareAndSet的含义?

答案:

// AtomicReference.compareAndSet
public final boolean compareAndSet(V expect, V update) {
    // 如果当前值 == expect
    // 则更新为update,返回true
    // 否则不更新,返回false
    return unsafe.compareAndSwapObject(...);
}

// 示例
AtomicInteger ai = new AtomicInteger(10);
ai.compareAndSet(10, 20);  // true, 10→20
ai.compareAndSet(10, 30);  // false, 当前是20

记忆要点:

  • compareAndSet(expect, update)
  • 期望值匹配则更新,不匹配则失败
  • 原子操作,线程安全

Q55: getAndIncrement的实现?

答案:

// AtomicInteger.getAndIncrement()
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

// Unsafe.getAndAddInt()
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);  // 读取当前值
    } while (!compareAndSwapInt(o, offset, v, v + delta));  // CAS
    return v;  // 返回旧值
}

// 工作流程:
// 1. v = 读取当前值
// 2. CAS(v, v+1)
// 3. 成功则返回v,失败则重试

记忆要点:

  • getAndIncrement = 先获取旧值,再自增
  • 实现原理:do-while + CAS循环
  • 原子操作,线程安全

Q56: incrementAndGet的实现?

答案:

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

// getAndIncrement()返回旧值
// incrementAndGet() = getAndIncrement() + 1
// 即返回新值

对比:

方法 返回值
getAndIncrement() 旧值
incrementAndGet() 新值
getAndAdd(delta) 旧值
addAndGet(delta) 新值

Q57-59: AtomicReference、AtomicStampedReference、AtomicMarkableReference

答案:

// AtomicReference
AtomicReference<String> ref = new AtomicReference<>("A");
ref.compareAndSet("A", "B");

// AtomicStampedReference - 解决ABA
AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
int stamp = ref.getStamp();
ref.compareAndSet("A", "B", stamp, stamp + 1);

// AtomicMarkableReference - 带标记
AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("A", false);
ref.compareAndSet("A", "B", false, true);

Q60: AtomicIntegerArray的作用?

答案:

// 原子数组,支持数组元素的原子操作
AtomicIntegerArray array = new AtomicIntegerArray(10);

array.set(0, 10);         // 设置值
array.getAndIncrement(0);  // 原子自增
array.compareAndSet(0, 10, 20);  // CAS

// 注意:传数组构造,会复制
int[] arr = {1, 2, 3};
AtomicIntegerArray atomicArr = new AtomicIntegerArray(arr);
arr[0] = 100;  // 不影响atomicArr

Q61-62: AtomicReferenceArray、AtomicIntegerFieldUpdater

答案:

// AtomicReferenceArray
AtomicReferenceArray<String> arr = new AtomicReferenceArray<>(10);
arr.compareAndSet(0, "A", "B");

// AtomicIntegerFieldUpdater - 更新对象的volatile字段
public class Example {
    volatile int counter;
}

static final AtomicIntegerFieldUpdater<Example> UPDATER =
    AtomicIntegerFieldUpdater.newUpdater(Example.class, "counter");

UPDATER.getAndIncrement(example);

Q63: LongAdder比AtomicLong快的原因?

答案:

AtomicLong的问题:

  • 所有线程竞争同一个value
  • 高并发时大量CAS失败,不断重试
  • CPU缓存一致性开销大

LongAdder的优化:

  • 分段计数,每个线程有独立的Cell
  • 减少竞争,最后汇总所有Cell
// LongAdder简化原理
public class LongAdder {
    // Cell数组,分散竞争
    Cell[] cells;

    // 基础值(无竞争时使用)
    volatile long base;

    public void increment() {
        Cell[] cs; Cell c; long b, v;
        if ((cs = cells) != null || (c = currentCell()) == null) {
            b = base;
            if (casBase(b, b + 1))  // 无竞争时用base
                return;
        }
        // 有竞争时用Cell
        if (c != null) {
            do {
                v = c.value;
            } while (!c.cas(v, v + 1));  // CAS当前线程的Cell
        }
    }

    public long sum() {
        // 汇和所有Cell
        long sum = base;
        for (Cell c : cells) {
            if (c != null) {
                sum += c.value;
            }
        }
        return sum;
    }
}

性能对比:

并发数: 1000线程

AtomicLong:
- 所有线程竞争同一个value
- 大量CAS失败重试
- 耗时: 5000ms

LongAdder:
- 分散到多个Cell
- 几乎无竞争
- 耗时: 500ms

LongAdder快约10倍!

记忆要点:

  • AtomicLong = 单一value,高并发竞争严重
  • LongAdder = 分段计数(Cell数组),减少竞争
  • 适用场景:高并发写操作
  • 不适用:频繁读取(sum()开销)

Q64: LongAdder的原理?

答案:

见Q63


Q65: DoubleAdder和LongAdder的区别?

答案:

LongAdder:  int/long类型
DoubleAdder: double类型

原理相同,只是数据类型不同

Q66-100: 锁机制剩余题目

由于篇幅限制,以下是剩余题目的简要答案:

Q66: 什么是可重入锁?

// 可重入 = 同一线程可以多次获取同一把锁
synchronized (this) {
    synchronized (this) {  // 可重入,不会死锁
        // ...
    }
}

Q67: ReentrantLock如何实现可重入?

// 通过计数器
public class ReentrantLock {
    private Thread owner;
    private int holdCount = 0;

    public void lock() {
        Thread current = Thread.currentThread();
        if (owner == current) {
            holdCount++;  // 重入,计数+1
            return;
        }
        // ... 其他线程等待
    }

    public void unlock() {
        holdCount--;
        if (holdCount == 0) {
            owner = null;
        }
    }
}

Q68-73: ReentrantLock相关

Q68: ReentrantLock的基本使用?

ReentrantLock lock = new ReentrantLock();

lock.lock();
try {
    // 临界区
} finally {
    lock.unlock();  // 必须在finally
}

Q69: lock()和tryLock()的区别?

方法 阻塞 返回值
lock() 阻塞直到获得锁 void
tryLock() 不阻塞,立即返回 boolean

Q70: tryLock(long timeout)?

if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        // 获得锁
    } finally {
        lock.unlock();
    }
} else {
    // 超时,未获得锁
}

Q71: lockInterruptibly()?

try {
    lock.lockInterruptibly();  // 可中断的锁获取
} catch (InterruptedException e) {
    // 被中断
}

Q72: 公平锁和非公平锁的区别?

// 公平锁:按请求顺序获取锁
ReentrantLock fairLock = new ReentrantLock(true);

// 非公平锁:随机获取(默认)
ReentrantLock unfairLock = new ReentrantLock(false);

Q73: 如何创建公平锁?

见Q72

Q74-77: Condition相关

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
try {
    while (!conditionMet()) {
        condition.await();  // 等待
    }
} finally {
    lock.unlock();
}

// 唤醒
lock.lock();
try {
    updateCondition();
    condition.signal();    // 唤醒一个
    condition.signalAll(); // 唤醒所有
} finally {
    lock.unlock();
}

Q78-82: ReadWriteLock相关

ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();   // 读锁
Lock writeLock = rwLock.writeLock(); // 写锁

// 读锁 - 共享
readLock.lock();
try {
    // 读操作
} finally {
    readLock.unlock();
}

// 写锁 - 独占
writeLock.lock();
try {
    // 写操作
} finally {
    writeLock.unlock();
}

Q83-89: StampedLock相关

StampedLock lock = new StampedLock();

// 乐观读
long stamp = lock.tryOptimisticRead();
double value = x + y;
if (!lock.validate(stamp)) {
    stamp = lock.readLock();  // 失败,转为悲观读
    try {
        value = x + y;
    } finally {
        lock.unlockRead(stamp);
    }
}

// 写锁
stamp = lock.writeLock();
try {
    x = newX;
    y = newY;
} finally {
    lock.unlockWrite(stamp);
}

Q90-100: 自旋锁、AQS等

Q90: 自旋锁的缺点?

  • CPU空转,浪费CPU
  • 无法获得锁的线程一直占用CPU
  • 长时间持有锁会导致其他线程饥饿

线程池(101-150)

Q101: 为什么需要线程池?

答案:

不用线程池 用线程池
每次任务创建新线程 复用线程,减少创建开销
无限制创建线程,可能OOM 控制最大线程数
难以管理任务执行 统一管理,监控
无法控制并发数 控制并发数,避免资源耗尽

创建线程开销:

  • 分配栈空间(默认1MB)
  • 初始化线程数据结构
  • 与OS交互
  • 约1-10ms

线程池优势:

  • 复用线程:省去创建销毁开销
  • 控制并发:避免资源耗尽
  • 统一管理:监控、统计、异常处理

Q102: ThreadPoolExecutor的核心参数?

答案:

public ThreadPoolExecutor(
    int corePoolSize,      // 核心线程数
    int maximumPoolSize,   // 最大线程数
    long keepAliveTime,    // 空闲线程存活时间
    TimeUnit unit,         // 时间单位
    BlockingQueue<Runnable> workQueue,  // 工作队列
    ThreadFactory threadFactory,         // 线程工厂
    RejectedExecutionHandler handler     // 拒绝策略
)
参数 说明 默认值
corePoolSize 核心线程数,常驻 0
maximumPoolSize 最大线程数 Integer.MAX_VALUE
keepAliveTime 非核心线程空闲存活时间 60L
unit 时间单位 SECONDS
workQueue 工作队列 SynchronousQueue
threadFactory 线程工厂 DefaultThreadFactory
handler 拒绝策略 AbortPolicy

Q103-110: 线程池参数详解

Q103: corePoolSize和maximumPoolSize的区别?**

corePoolSize maximumPoolSize
核心线程数,常驻 最大线程数,临时创建
< corePoolSize 创建新线程
corePoolSize < size < maximumPoolSize 放入队列,队列满才创建
线程数 < corePoolSize: 创建新线程
corePoolSize <= 线程数: 放入队列
队列满 && 线程数 < maximumPoolSize: 创建新线程
队列满 && 线程数 >= maximumPoolSize: 拒绝

Q104: keepAliveTime的作用?**

非核心线程空闲超过keepAliveTime被回收


Q105: 什么是工作队列?**

保存等待执行的任务队列


Q106: 常见的工作队列类型?**

队列类型 特点 适用场景
LinkedBlockingQueue 无界队列 任务量大
ArrayBlockingQueue 有界队列 防止OOM
SynchronousQueue 不存储,直接传递 高并发
PriorityBlockingQueue 优先级队列 需要排序

Q107: 线程池的工作流程?

          提交任务
             │
             ▼
    核心线程数满?
     ┌─否──┬─是─┐
     ▼      ▼    │
  创建核心  队列满?│
   线程    ├否─┬─┴┐
            │   ▼  ▼
          放入 创建 拒绝
          队列 非核心
          线程 线程

Q108: 线程池的拒绝策略有哪些?

策略 行为
AbortPolicy 抛出RejectedExecutionException
CallerRunsPolicy 由提交任务的线程执行
DiscardPolicy 丢弃任务
DiscardOldestPolicy 丢弃队列中最老的任务

Q109-150: 线程池、CompletableFuture等


并发集合(151-200)

Q151: HashMap是线程安全的吗?**

不是!多线程并发修改会导致数据不一致或死循环。

HashMap线程安全问题:

public class HashMapUnsafe {
    private static final Map<Integer, Integer> map = new HashMap<>();

    public static void main(String[] args) throws Exception {
        Runnable task = () -> {
            for (int i = 0; i < 10000; i++) {
                map.put(i, i);
            }
        };

        new Thread(task).start();
        new Thread(task).start();

        Thread.sleep(1000);
        System.out.println(map.size());  // 可能 < 20000
    }
}

解决方案:

  1. Collections.synchronizedMap(new HashMap<>())
  2. ConcurrentHashMap(推荐)
  3. 手动加锁

Q152: ConcurrentHashMap的实现原理?

Java 7(分段锁):

ConcurrentHashMap {
    Segment[] segments;  // 分段数组,默认16段
}

Segment extends ReentrantLock {
    HashEntry[] table;  // 每段独立加锁
}

// 只锁一个段,其他段可并发访问

Java 8+(CAS + synchronized):

ConcurrentHashMap {
    Node<K,V>[] table;  // Node数组
}

Node {
    int hash;
    K key;
    V value;
    Node<K,V> next;
}

// 只锁一个Node,并发度更高

对比:

版本 锁机制 并发度
Java 7 分段锁(16段) 16
Java 8+ Node级别 数组长度

Q153-200: 并发集合相关


并发工具(201-250)

Q201: CountDownLatch的作用?**

倒计时门闩,等待多个任务完成。

CountDownLatch latch = new CountDownLatch(3);  // 等待3个任务

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        try {
            doWork();
        } finally {
            latch.countDown();  // 完成一个
        }
    }).start();
}

latch.await();  // 等待所有完成

Q202-250: 并发工具类


高级篇(251-300)

Q251: 什么是死锁?

多个线程互相持有对方需要的锁,导致永久阻塞。

public class Deadlock {
    private static final Object lockA = new Object();
    private static final Object lockB = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lockA) {
                System.out.println("Thread1: 获得A");
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized (lockB) {
                    System.out.println("Thread1: 获得B");
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (lockB) {
                System.out.println("Thread2: 获得B");
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized (lockA) {
                    System.out.println("Thread2: 获得A");
                }
            }
        }).start();
    }
}

死锁四条件:

  1. 互斥:资源独占
  2. 持有并等待:持有一个,等待另一个
  3. 不可剥夺:不能强制释放
  4. 循环等待:形成循环链

Q252-300: 高级篇剩余


总结

记忆要点总览

类别 核心要点
线程 5种状态、start vs run、interrupt
synchronized、volatile、CAS、AQS
线程池 7大参数、工作流程、拒绝策略
集合 ConcurrentHashMap原理
工具 CountDownLatch、Semaphore、CompletableFuture

重点掌握

  1. synchronized原理:锁升级、锁释放
  2. volatile作用:可见性、有序性
  3. CAS原理:硬件原子操作
  4. 线程池参数:corePoolSize、maximumPoolSize、workQueue
  5. ConcurrentHashMap:Java 7 vs Java 8实现
  6. CountDownLatch、CyclicBarrier、Semaphore:使用场景
  7. CompletableFuture:异步编程
  8. JMM:happens-before规则

持续更新中...

close
arrow_upward