前言

并发编程是Java面试中的难点和重点,也是区分初中高级程序员的关键。
掌握并发不仅是为了面试,更是为了写出高性能、高可用的系统。

一、Java并发基础

1.1 并发 vs 并行
面试高频辨析:并发和并行的区别是什么?

java
// 并发:同一时间段内多个任务交替执行(单核CPU)
// 并行:同一时刻多个任务同时执行(多核CPU)

// 生活类比:
// 并发:一个咖啡师交替服务多个顾客
// 并行:多个咖啡师同时服务多个顾客
1.2 线程状态转换
java
public class ThreadStateDemo {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000); // TIMED_WAITING
synchronized (ThreadStateDemo.class) {
ThreadStateDemo.class.wait(); // WAITING
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});

    System.out.println("创建后: " + thread.getState());  // NEW
    
    thread.start();
    System.out.println("启动后: " + thread.getState());  // RUNNABLE
    
    Thread.sleep(100);
    System.out.println("sleep中: " + thread.getState());  // TIMED_WAITING
    
    Thread.sleep(2000);
    synchronized (ThreadStateDemo.class) {
        ThreadStateDemo.class.notify();
    }
    
    thread.join();
    System.out.println("结束后: " + thread.getState());  // TERMINATED
}

}
线程生命周期图:

text
NEW → RUNNABLE → (BLOCKED/WAITING/TIMED_WAITING) → TERMINATED
↑ ↓
←←←←←←←←←←←←←

二、synchronized深度解析

2.1 实现原理
面试题:synchronized底层是如何实现的?

java
// 字节码层面分析
public class SynchronizedDemo {
// 同步代码块
public void method1() {
synchronized (this) {
System.out.println(“同步代码块”);
}
}

// 同步方法
public synchronized void method2() {
    System.out.println("同步方法");
}

}
编译后的字节码:

text
method1():
monitorenter // 进入同步块
// 代码逻辑
monitorexit // 正常退出
monitorexit // 异常退出(保证释放锁)

method2():
ACC_SYNCHRONIZED // 方法访问标志
2.2 锁升级过程(JDK 1.6+优化)
面试必考:偏向锁 → 轻量级锁 → 重量级锁

java
public class LockUpgrade {
private static Object lock = new Object();

public static void main(String[] args) {
    // 第一阶段:偏向锁(只有一个线程访问)
    new Thread(() -> {
        synchronized (lock) {
            System.out.println("线程1首次获取锁 - 偏向锁");
        }
    }).start();
    
    // 第二阶段:轻量级锁(多个线程交替访问)
    new Thread(() -> {
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        synchronized (lock) {
            System.out.println("线程2竞争锁 - 轻量级锁");
        }
    }).start();
    
    // 第三阶段:重量级锁(激烈竞争)
    for (int i = 3; i <= 10; i++) {
        final int id = i;
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程" + id + "竞争锁 - 重量级锁");
                try { Thread.sleep(1000); } catch (InterruptedException e) {}
            }
        }).start();
    }
}

}
锁升级过程总结:

偏向锁:Mark Word记录线程ID(只有一个线程)

轻量级锁:CAS自旋尝试获取锁(少量竞争)

重量级锁:操作系统互斥量(激烈竞争)

2.3 synchronized的缺点
不可中断:线程会一直等待

非公平锁:可能导致线程饥饿

单一条件:无法实现复杂同步

三、volatile关键字

3.1 内存可见性
面试题:volatile如何保证可见性?

java
public class VisibilityDemo {
// 不加volatile,子线程可能看不到主线程的修改
private static /* volatile */ boolean flag = true;

public static void main(String[] args) throws Exception {
    new Thread(() -> {
        System.out.println("子线程启动");
        while (flag) {
            // 空循环
        }
        System.out.println("子线程结束");
    }).start();
    
    Thread.sleep(1000);
    flag = false;
    System.out.println("主线程修改flag为false");
}

}
加上volatile后的内存语义:

text
写操作:将工作内存的值刷新到主内存
读操作:从主内存读取最新值
3.2 禁止指令重排序
面试题:单例模式双重检查锁为什么要加volatile?

java
public class Singleton {
// 必须加volatile
private static volatile Singleton instance;

private Singleton() {}

public static Singleton getInstance() {
    if (instance == null) {  // 第一次检查
        synchronized (Singleton.class) {
            if (instance == null) {  // 第二次检查
                instance = new Singleton();
                // 不加volatile可能的执行顺序:
                // 1. 分配内存空间
                // 2. 引用赋值(instance不为null了)
                // 3. 初始化对象  ← 可能重排序到后面
            }
        }
    }
    return instance;
}

}

四、AQS(AbstractQueuedSynchronizer)框架

4.1 AQS核心原理
面试题:AQS是如何实现同步的?

java
// AQS简化版实现思路
public abstract class SimpleAQS {
private volatile int state; // 同步状态
private Node head; // 等待队列头节点
private Node tail; // 等待队列尾节点

static final class Node {
    Thread thread;
    Node prev;
    Node next;
    int waitStatus;  // 等待状态
}

// 子类需要实现的方法
protected boolean tryAcquire(int arg) { return false; }
protected boolean tryRelease(int arg) { return false; }

// 获取锁
public final void acquire(int arg) {
    if (!tryAcquire(arg) && 
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
        // 获取失败,加入队列等待
        Thread.currentThread().interrupt();
    }
}

}
4.2 ReentrantLock实现分析
java
public class ReentrantLockDemo {
private static final ReentrantLock lock = new ReentrantLock(true); // 公平锁

public static void main(String[] args) {
    // 1. 可重入性演示
    lock.lock();
    try {
        System.out.println("第一次获取锁");
        lock.lock();  // 可重入
        try {
            System.out.println("第二次获取锁(重入)");
        } finally {
            lock.unlock();
        }
    } finally {
        lock.unlock();
    }
    
    // 2. 尝试获取锁
    if (lock.tryLock()) {
        try {
            System.out.println("获取锁成功");
        } finally {
            lock.unlock();
        }
    } else {
        System.out.println("获取锁失败");
    }
    
    // 3. 可中断获取锁
    Thread t = new Thread(() -> {
        try {
            lock.lockInterruptibly();
            try {
                Thread.sleep(5000);
            } finally {
                lock.unlock();
            }
        } catch (InterruptedException e) {
            System.out.println("被中断");
        }
    });
    
    t.start();
    try { Thread.sleep(1000); } catch (InterruptedException e) {}
    t.interrupt();  // 中断等待
}

}
4.3 ReentrantLock vs synchronized
特性 synchronized ReentrantLock
实现机制 JVM内置 Java代码实现
锁类型 非公平锁 公平/非公平可选
可中断 ❌ 不支持 ✅ 支持
超时机制 ❌ 不支持 ✅ 支持
条件队列 单个 多个
性能 JDK6后优化相当 高度竞争时更好

五、线程池深度剖析

5.1 线程池参数详解
面试题:线程池各个参数的含义是什么?

java
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // corePoolSize: 核心线程数(长期存活的线程)
10, // maximumPoolSize: 最大线程数
60L, // keepAliveTime: 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(100), // workQueue: 工作队列
Executors.defaultThreadFactory(), // threadFactory: 线程工厂
new ThreadPoolExecutor.AbortPolicy() // handler: 拒绝策略
);
5.2 线程池工作流程
text

  1. 提交任务

  2. 核心线程未满 → 创建新线程执行

  3. 核心线程已满 → 加入工作队列

  4. 队列已满且线程数未达最大 → 创建新线程执行

  5. 队列已满且线程数已达最大 → 执行拒绝策略
    5.3 四种拒绝策略
    java
    public class RejectionPolicyDemo {
    public static void main(String[] args) {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2, 2, 0, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(2)
    );

     // 1. AbortPolicy(默认):抛出异常
     executor.setRejectedExecutionHandler(
         new ThreadPoolExecutor.AbortPolicy());
     
     // 2. CallerRunsPolicy:调用者线程执行
     // 3. DiscardPolicy:直接丢弃
     // 4. DiscardOldestPolicy:丢弃队列最老任务
     
     for (int i = 0; i < 10; i++) {
         final int taskId = i;
         try {
             executor.execute(() -> {
                 System.out.println(Thread.currentThread().getName() 
                     + " 执行任务 " + taskId);
                 try { Thread.sleep(1000); } catch (InterruptedException e) {}
             });
         } catch (RejectedExecutionException e) {
             System.out.println("任务 " + taskId + " 被拒绝");
         }
     }
     
     executor.shutdown();
    

    }
    }
    5.4 线程池最佳实践
    java
    public class ThreadPoolBestPractice {
    // 1. 根据任务类型选择线程池
    private static final ExecutorService ioPool = Executors.newCachedThreadPool();
    private static final ExecutorService cpuPool = Executors.newFixedThreadPool(
    Runtime.getRuntime().availableProcessors()
    );

    // 2. 自定义线程工厂
    private static final ThreadFactory namedThreadFactory =
    new ThreadFactoryBuilder()
    .setNameFormat(“business-thread-%d”)
    .setDaemon(true)
    .build();

    // 3. 监控线程池状态
    public static void monitor(ThreadPoolExecutor executor) {
    System.out.println("核心线程数: " + executor.getCorePoolSize());
    System.out.println("活动线程数: " + executor.getActiveCount());
    System.out.println("队列大小: " + executor.getQueue().size());
    System.out.println("完成任务数: " + executor.getCompletedTaskCount());
    }
    }

六、并发集合类

6.1 ConcurrentHashMap实现原理
面试题:JDK 1.7和1.8的实现有什么区别?

java
public class ConcurrentHashMapDemo {
public static void main(String[] args) {
// JDK 1.8实现:数组 + 链表/红黑树 + CAS + synchronized
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

    // 1. put操作:CAS + synchronized
    map.put("key1", 1);
    
    // 2. 线程安全的遍历
    for (Map.Entry<String, Integer> entry : map.entrySet()) {
        System.out.println(entry.getKey() + ": " + entry.getValue());
    }
    
    // 3. 原子操作
    map.computeIfAbsent("key2", k -> 2);
    map.computeIfPresent("key1", (k, v) -> v + 10);
    
    // 4. 并发计数
    LongAdder adder = new LongAdder();  // 比AtomicLong性能更好
    adder.add(100);
    System.out.println("计数: " + adder.sum());
}

}
6.2 CopyOnWriteArrayList适用场景
java
public class CopyOnWriteDemo {
private static final List list =
new CopyOnWriteArrayList<>(); // 读多写少场景

public static void main(String[] args) {
    // 初始化数据
    list.add("A");
    list.add("B");
    list.add("C");
    
    // 多个线程同时读(不需要加锁)
    for (int i = 0; i < 5; i++) {
        new Thread(() -> {
            for (String s : list) {  // 快照迭代
                System.out.println(Thread.currentThread().getName() 
                    + " 读取: " + s);
            }
        }).start();
    }
    
    // 写操作(复制新数组)
    new Thread(() -> {
        list.add("D");  // 创建新数组,不影响正在读的线程
        System.out.println("添加新元素");
    }).start();
}

}

七、JUC工具类实战

7.1 CountDownLatch(倒计时锁)
java
public class CountDownLatchDemo {
public static void main(String[] args) throws Exception {
// 场景:主线程等待所有子线程完成
CountDownLatch latch = new CountDownLatch(3);

    for (int i = 1; i <= 3; i++) {
        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() 
                    + " 开始执行");
                Thread.sleep((long) (Math.random() * 3000));
                System.out.println(Thread.currentThread().getName() 
                    + " 执行完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown();  // 计数器减1
            }
        }, "线程-" + i).start();
    }
    
    System.out.println("主线程等待...");
    latch.await();  // 阻塞直到计数器为0
    System.out.println("所有线程执行完成,主线程继续");
}

}
7.2 CyclicBarrier(循环屏障)
java
public class CyclicBarrierDemo {
public static void main(String[] args) {
// 场景:多个线程相互等待,达到屏障点后继续
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println(“所有线程到达屏障,执行回调任务”);
});

    for (int i = 0; i < 3; i++) {
        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() 
                    + " 到达屏障前");
                barrier.await();  // 等待其他线程
                System.out.println(Thread.currentThread().getName() 
                    + " 通过屏障后");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }
}

}
7.3 Semaphore(信号量)
java
public class SemaphoreDemo {
public static void main(String[] args) {
// 场景:控制同时访问资源的线程数
Semaphore semaphore = new Semaphore(3); // 允许3个线程同时访问

    for (int i = 1; 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) {
                e.printStackTrace();
            } finally {
                semaphore.release();  // 释放许可
            }
        }, "线程-" + i).start();
    }
}

}
7.4 CompletableFuture(异步编程)
java
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
// 1. 异步执行
CompletableFuture future1 = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
return “Hello”;
});

    // 2. 链式调用
    CompletableFuture<String> future2 = future1.thenApplyAsync(s -> {
        return s + " World";
    });
    
    // 3. 组合多个Future
    CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Java");
    CompletableFuture<String> future4 = future2.thenCombine(future3, 
        (s1, s2) -> s1 + " " + s2);
    
    // 4. 异常处理
    CompletableFuture<String> future5 = future4.exceptionally(e -> {
        System.out.println("发生异常: " + e.getMessage());
        return "默认值";
    });
    
    System.out.println("最终结果: " + future5.get());
    
    // 5. 所有任务完成
    CompletableFuture<Void> all = CompletableFuture.allOf(
        future1, future2, future3, future4);
    all.join();
}

}

八、面试实战技巧

8.1 死锁问题分析
面试题:如何定位和解决死锁?

java
public class DeadLockDemo {
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("线程1获取lockA");
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            synchronized (lockB) {
                System.out.println("线程1获取lockB");
            }
        }
    }).start();
    
    new Thread(() -> {
        synchronized (lockB) {
            System.out.println("线程2获取lockB");
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            synchronized (lockA) {
                System.out.println("线程2获取lockA");
            }
        }
    }).start();
}

}
死锁排查命令:

bash

1. 查看Java进程ID

jps -l

2. 查看线程堆栈

jstack

3. 查找死锁信息(Found one Java-level deadlock)

8.2 并发问题定位工具
jstack:线程堆栈分析

jconsole:图形化监控

VisualVM:性能分析

Arthas:在线诊断工具

📚 本章总结
知识点 掌握要点 面试权重
synchronized 实现原理、锁升级 ⭐⭐⭐⭐⭐
volatile 可见性、禁止重排序 ⭐⭐⭐⭐
AQS框架 实现原理、ReentrantLock ⭐⭐⭐⭐⭐
线程池 参数配置、拒绝策略 ⭐⭐⭐⭐⭐
JUC工具 CountDownLatch/CyclicBarrier等 ⭐⭐⭐⭐

🎯 下期预告

《Java面试通关指南(三):JVM与性能调优篇》

JVM内存模型深度解析

垃圾回收算法与实现

JVM性能监控工具

线上问题排查实战

💪 练习题
代码分析题:以下代码有什么问题?如何改进?

java
public class ThreadLocalLeak {
private static ThreadLocal<Map<String, Object>> threadLocal =
ThreadLocal.withInitial(HashMap::new);

public void setData(String key, Object value) {
    threadLocal.get().put(key, value);
}

// 缺少remove操作,可能导致内存泄漏

}
设计题:如何实现一个生产者-消费者模型?

算法题:使用两个线程交替打印奇偶数

💡 面试小贴士
遇到并发问题:先分析是哪种并发问题(竞态条件、死锁、活锁等)

回答锁相关:从使用场景、性能、功能三个维度比较

线程池配置:结合具体业务场景(IO密集型 vs CPU密集型)

展现思考过程:即使不知道完整答案,也要展示分析思路

记住:面试官更看重你的思考过程,而不仅仅是正确答案!

本文为《Java面试通关指南》系列第二篇,后续将继续更新JVM、Spring、分布式等主题
关注作者,不错过最新干货!评论区留下你想了解的主题 🚀

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐