技术面Java并发上下文切换、线程安全、并发与并行、守护线程、虚拟线程在多核处理器普及的今天并发编程已成为后端开发的必备技能。Java作为企业级应用的主流语言其并发机制经历了从线程到锁、再到虚拟线程的演进。本文将从实战角度深入剖析Java并发的核心概念并通过可运行的代码示例帮助你理解这些技术点。### 并发与并行概念辨析与实战并发Concurrency和并行Parallelism常被混淆。简单来说并发是多个任务在同一时间段内交替执行而并行是多个任务在同一时刻同时执行。Java通过Thread类或ExecutorService来实现并发而并行则依赖多核CPU。以下代码演示了并发与并行的区别通过单线程模拟并发时间片轮转以及多线程模拟并行实际同时运行。javaimport java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ConcurrencyVsParallelism { public static void main(String[] args) { // 演示并发单线程交替执行任务 System.out.println( 并发演示单线程时间片轮转 ); Runnable task1 () - { for (int i 0; i 3; i) { System.out.println(任务1 - 第 (i1) 次执行); // 模拟I/O阻塞让出CPU try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } }; Runnable task2 () - { for (int i 0; i 3; i) { System.out.println(任务2 - 第 (i1) 次执行); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } }; // 使用两个线程实现并发实际是交替执行 Thread t1 new Thread(task1); Thread t2 new Thread(task2); t1.start(); t2.start(); // 等待两个线程结束 try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } // 演示并行使用线程池让多个任务同时运行需多核CPU System.out.println(\n 并行演示线程池多核执行 ); ExecutorService executor Executors.newFixedThreadPool(2); executor.submit(() - { for (int i 0; i 3; i) { System.out.println(并行任务A - 第 (i1) 次执行); try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } }); executor.submit(() - { for (int i 0; i 3; i) { System.out.println(并行任务B - 第 (i1) 次执行); try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } }); executor.shutdown(); }}运行结果分析在单核CPU上两个线程会交替输出并发在多核CPU上两个线程可能同时输出并行。核心区别在于是否真正同时执行。### 上下文切换代价与优化上下文切换是指CPU从一个线程切换到另一个线程时保存当前线程状态并加载新线程状态的过程。频繁切换会带来性能开销如缓存失效、寄存器保存等。在Java中可以通过减少锁竞争、使用无锁数据结构来降低上下文切换。以下代码演示了上下文切换对性能的影响高竞争锁导致频繁切换。javaimport java.util.concurrent.atomic.AtomicInteger;public class ContextSwitchDemo { private static final int THREAD_COUNT 10; private static final int ITERATIONS 100000; // 使用synchronized锁导致高竞争 private static int counter 0; // 使用无锁AtomicInteger减少上下文切换 private static AtomicInteger atomicCounter new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { // 测试synchronized版本高上下文切换 long start System.currentTimeMillis(); Thread[] threads new Thread[THREAD_COUNT]; for (int i 0; i THREAD_COUNT; i) { threads[i] new Thread(() - { for (int j 0; j ITERATIONS; j) { synchronized (ContextSwitchDemo.class) { counter; // 锁竞争导致线程阻塞和切换 } } }); threads[i].start(); } for (Thread t : threads) t.join(); long syncTime System.currentTimeMillis() - start; System.out.println(synchronized版本耗时: syncTime ms, 结果: counter); // 测试AtomicInteger版本无锁减少上下文切换 start System.currentTimeMillis(); for (int i 0; i THREAD_COUNT; i) { threads[i] new Thread(() - { for (int j 0; j ITERATIONS; j) { atomicCounter.incrementAndGet(); // CAS操作无锁竞争 } }); threads[i].start(); } for (Thread t : threads) t.join(); long atomicTime System.currentTimeMillis() - start; System.out.println(AtomicInteger版本耗时: atomicTime ms, 结果: atomicCounter.get()); }}运行结果分析synchronized版本因线程阻塞导致频繁上下文切换而AtomicInteger基于CAS比较并交换无锁操作显著减少了切换次数性能提升明显。### 线程安全从原理到实战线程安全指多个线程访问共享数据时不会出现数据不一致或脏读。Java中通过synchronized、Lock、volatile、原子类等实现。以下代码展示了一个典型的线程安全问题——竞态条件并给出解决方案。javaimport java.util.concurrent.locks.ReentrantLock;public class ThreadSafetyDemo { private static int sharedCounter 0; // 非线程安全 private static final ReentrantLock lock new ReentrantLock(); // 可重入锁 public static void main(String[] args) throws InterruptedException { // 不安全版本多线程同时修改导致数据不一致 Thread[] threads new Thread[5]; for (int i 0; i 5; i) { threads[i] new Thread(() - { for (int j 0; j 1000; j) { sharedCounter; // 非原子操作读取-修改-写入 } }); threads[i].start(); } for (Thread t : threads) t.join(); System.out.println(不安全版本结果期望5000: sharedCounter); // 安全版本使用锁保护共享变量 sharedCounter 0; for (int i 0; i 5; i) { threads[i] new Thread(() - { for (int j 0; j 1000; j) { lock.lock(); try { sharedCounter; // 原子操作线程安全 } finally { lock.unlock(); } } }); threads[i].start(); } for (Thread t : threads) t.join(); System.out.println(安全版本结果期望5000: sharedCounter); }}关键点sharedCounter并非原子操作它包含读取、加1、写入三步。多线程同时执行时可能读取到旧值导致最终结果小于预期。使用ReentrantLock或synchronized确保同一时刻只有一个线程执行该代码块。### 守护线程后台服务的生命周期守护线程Daemon Thread是Java中一种特殊的线程当所有用户线程结束时JVM会自动退出无论守护线程是否运行。典型应用包括垃圾回收、日志记录等后台服务。以下代码演示了守护线程的行为。javapublic class DaemonThreadDemo { public static void main(String[] args) throws InterruptedException { // 创建守护线程模拟后台日志服务 Thread daemonThread new Thread(() - { int count 0; while (true) { System.out.println(守护线程运行中... 第 (count) 次); try { Thread.sleep(500); } catch (InterruptedException e) { break; } } }); daemonThread.setDaemon(true); // 设置为守护线程 daemonThread.start(); // 主线程用户线程运行3秒后结束 System.out.println(主线程开始工作3秒后结束); Thread.sleep(3000); System.out.println(主线程结束JVM退出守护线程自动终止); }}运行结果守护线程会在主线程结束后自动终止不会输出“守护线程运行中… 第7次”。注意守护线程中应避免执行I/O操作或事务否则JVM退出时可能导致数据丢失。### 虚拟线程Java 21的革命性特性虚拟线程Virtual Threads是JDK 21正式推出的轻量级线程由JVM管理而非操作系统。它解决了传统平台线程Platform Thread资源昂贵、上下文切换开销大的问题。每个虚拟线程仅占用几KB内存可创建数十万个而不会导致系统崩溃。以下代码演示了虚拟线程的高并发能力创建10万个虚拟线程执行简单任务。javaimport java.util.concurrent.Executors;public class VirtualThreadDemo { public static void main(String[] args) throws InterruptedException { // 传统平台线程创建10万个线程会耗尽系统资源 long start System.currentTimeMillis(); Thread[] platformThreads new Thread[100000]; for (int i 0; i 100000; i) { platformThreads[i] new Thread(() - { // 模拟轻量级计算 int sum 0; for (int j 0; j 10; j) sum j; }); platformThreads[i].start(); } for (Thread t : platformThreads) t.join(); long platformTime System.currentTimeMillis() - start; System.out.println(平台线程耗时: platformTime ms可能因资源不足而失败); // 虚拟线程轻松承载10万个任务 start System.currentTimeMillis(); try (var executor Executors.newVirtualThreadPerTaskExecutor()) { for (int i 0; i 100000; i) { executor.submit(() - { int sum 0; for (int j 0; j 10; j) sum j; }); } } // 自动等待所有任务完成 long virtualTime System.currentTimeMillis() - start; System.out.println(虚拟线程耗时: virtualTime ms); }}注意事项运行平台线程版本时可能会因为系统线程资源限制而抛出OutOfMemoryError。虚拟线程版本则能顺利执行且耗时更短尤其在I/O密集型场景下优势更明显。虚拟线程适合高并发的网络应用、微服务等场景。### 总结Java并发编程经历了从基础线程到高级锁、再到虚拟线程的演进。上下文切换是性能瓶颈的核心通过无锁编程和虚拟线程可以显著降低切换成本线程安全需要谨慎处理共享数据的原子性守护线程适合后台任务而虚拟线程作为Java并发的新里程碑为高并发应用提供了轻量级解决方案。在实际开发中建议- 优先使用虚拟线程JDK 21避免手动管理线程池。- 对共享数据采用原子类或ReentrantLock避免synchronized的过度竞争。- 利用ThreadLocal隔离线程上下文减少锁的使用。- 合理设置线程池大小I/O密集型可大些CPU密集型不宜超过核心数。掌握这些概念和实战技巧你就能在技术面试中自信地应对Java并发相关的问题。