定位:JUC是Java标准库提供的并发编程工具包,包含任务封装、可重入锁、原子类、线程池、同步工具等,比原生
synchronized更灵活、功能更强。
1. Callable 接口 + FutureTask
解决的问题
Runnable的void run()没有返回值,也不能抛出受检异常;如果需要线程执行完带回结果,原生写法需要自己写同步、等待、结果类,代码繁琐易错。
Callable 接口(适合于想让某个线程执行一个逻辑,并且返回结果的时候相比之下Runnable不关注结果)
public interface Callable<V> { V call() throws Exception; }- 带泛型
V,表示返回值的类型 call()方法可以返回结果,也可以抛出异常
FutureTask
Callable不能直接传给Thread执行,需要用FutureTask包装。
- 作用:保存Callable的执行结果;主线程可以通过它阻塞等待任务完成并获取结果。
- 通俗比喻:点麻辣烫,后厨开始做,前台给你一张小票(FutureTask),随时可以凭小票看做好了没。
完整代码示例
// 定义带返回值的任务:计算1~1000的和 Callable<Integer> callable = new Callable<Integer>() { @Override public Integer call() throws Exception { int sum = 0; for (int i = 1; i <= 1000; i++) { sum += i; } return sum; } }; FutureTask<Integer> futureTask = new FutureTask<>(callable); new Thread(futureTask).start(); // 主线程阻塞等待,直到任务完成拿到结果 //此处的get就能获取到callable里面的 返回结果 //由于线程是并发执行的,执行到主线程的get的时候,t线程可能还没有执行完 //没有执行完的话就会阻塞 Integer result = futureTask.get(); System.out.println(result);面试核心点
Runnable:无返回值,不抛出受检异常Callable:有返回值,可抛出异常,必须搭配FutureTask使用futureTask.get():阻塞等待任务执行完成,获取返回值
2. ReentrantLock 可重入互斥锁
和synchronized定位相同,都是实现线程互斥,保证线程安全;但ReentrantLock是Java类实现,功能更灵活。
基础用法
ReentrantLock lock = new ReentrantLock(); lock.lock(); // 加锁,拿不到锁就死等 try { // 临界区业务代码 } finally { lock.unlock(); // 必须手动解锁,写在finally防止异常遗漏 }增强功能
- tryLock(long timeout):尝试加锁,等待指定时间还拿不到锁就放弃,不会死等。
- 公平锁支持:构造方法传入
true即可开启公平锁,遵守先来后到。ReentrantLock fairLock = new ReentrantLock(true); // 公平锁 - 精准唤醒:搭配
Condition类,可以指定唤醒某个/某组等待线程;而synchronized的notify()只能随机唤醒一个。
synchronized vs ReentrantLock 对比(面试必背)
| 维度 | synchronized | ReentrantLock |
|---|---|---|
| 实现层面 | JVM关键字,底层C++实现 | JDK标准库类,Java代码实现 |
| 锁释放 | 退出同步块自动释放 | 必须手动调用unlock |
| 加锁行为 | 拿不到锁就死等 | lock()死等;tryLock可超时放弃 |
| 公平性 | 只能非公平锁 | 默认非公平,可手动开启公平锁 |
| 等待唤醒 | wait/notify,随机唤醒 | Condition,可精准唤醒指定线程 |
| 锁类型 | 可重入锁 | 可重入锁 |
选型建议
- 锁竞争不激烈、代码简单:优先用
synchronized,自动释放不易出错 - 锁竞争激烈、需要超时放弃、公平锁、精准唤醒:用
ReentrantLock
3. 原子类(java.util.concurrent.atomic)
原理
内部基于CAS + volatile实现,不需要加锁就能完成线程安全的数值操作,性能远高于synchronized。
常见原子类
AtomicBoolean:原子布尔值AtomicInteger:原子整数AtomicLong:原子长整型AtomicReference:原子引用对象AtomicStampedReference:带版本号的原子引用,解决ABA问题
AtomicInteger 常用方法
| 方法 | 作用 | 对应普通操作 |
|---|---|---|
getAndIncrement() | 返回旧值,再自增 | i++ |
incrementAndGet() | 先自增,返回新值 | ++i |
getAndDecrement() | 返回旧值,再自减 | i-- |
decrementAndGet() | 先自减,返回新值 | --i |
addAndGet(int delta) | 加上delta,返回新值 | i += delta |
面试考点
AtomicInteger底层是CAS自旋实现的乐观锁,不需要重量级锁,高并发下性能优于synchronized。
4. 线程池
核心思想
线程创建销毁比进程轻量,但频繁创建销毁依然有开销。线程池提前创建一批线程复用,任务来了直接分配线程执行,任务完成线程不销毁,放回池中等待下一个任务。
Executors 工厂类(快捷创建线程池)
Executors是线程池工厂类,提供4种常用线程池,底层都是封装ThreadPoolExecutor。
| 工厂方法 | 特点 |
|---|---|
newFixedThreadPool(n) | 固定线程数量的线程池 |
newCachedThreadPool() | 线程数动态伸缩,空闲线程缓存复用 |
newSingleThreadExecutor() | 只有一个线程,任务串行执行 |
newScheduledThreadPool(n) | 支持延迟、定时执行任务,多线程版定时器 |
注意:生产环境不推荐直接用Executors,因为部分线程池队列无界、最大线程数无界,高并发下容易OOM。
ThreadPoolExecutor 七大核心参数(面试必考)
可以理解为开一家公司:
corePoolSize核心线程数:正式员工,常驻不辞退maximumPoolSize最大线程数:正式员工 + 临时工的总上限keepAliveTime空闲存活时间:临时工空闲多久就被辞退TimeUnit unit:时间单位BlockingQueue<Runnable> workQueue:阻塞任务队列,存放等待执行的任务ThreadFactory threadFactory:线程工厂,统一创建线程,可自定义线程名称RejectedExecutionHandler handler:拒绝策略,任务超过承载时的处理方式
四种拒绝策略
AbortPolicy【默认】:直接抛出RejectedExecutionException异常CallerRunsPolicy:由提交任务的线程自己执行这个任务DiscardOldestPolicy:丢弃队列中排队最久的任务,再尝试提交新任务DiscardPolicy:直接丢弃新来的任务,不抛异常
线程池执行流程(必背)
- 提交任务 → 核心线程未满 → 创建核心线程执行任务
- 核心线程已满 → 任务进入阻塞队列排队
- 队列也满了 → 创建非核心(临时)线程执行任务
- 总线程数达到最大线程数 → 触发拒绝策略
- 任务高峰过后,非核心线程空闲超过
keepAliveTime→ 销毁回收,核心线程保留
5. Semaphore 信号量
锁本质也是一种特殊的信号量,加锁操作p操作,1->0 ,解锁操作V操作0->1 (二元信号量)
核心作用
表示可用资源的个数,本质是一个原子计数器,控制同时访问资源的最大线程数。
PV操作
acquire()P操作:申请一个资源,计数器-1;计数器为0则阻塞等待release()V操作:释放一个资源,计数器+1,唤醒等待的线程
通俗比喻(PDF原文)
停车场剩余车位牌。车开进去车位-1,开出来车位+1;车位为0时,门口的车等待。
代码示例
// 初始4个许可,最多4个线程同时执行 Semaphore semaphore = new Semaphore(4); Runnable task = () -> { try { semaphore.acquire(); // 申请资源 System.out.println("获取到资源,执行任务"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); // 释放资源 } }; // 启动20个线程,最多同时4个运行 for (int i = 0; i < 20; i++) { new Thread(task).start(); }适用场景
限流、控制并发数、共享资源池管理。开发中需要申请资源的场景
6. CountDownLatch 倒计时锁存器
核心作用
等待N个任务全部执行完成,主线程再继续往下走。
核心方法
CountDownLatch(int count):构造方法,指定需要等待的任务数量countDown():每个任务执行完调用一次,计数器-1await():主线程调用,阻塞等待,直到计数器减到0
通俗比喻
跑步比赛:10个选手就位,枪响同时出发;必须等所有选手都冲过终点,才能公布成绩。
代码示例
// 等待10个任务全部完成 CountDownLatch latch = new CountDownLatch(10); Runnable task = () -> { try { Thread.sleep((long) (Math.random() * 1000)); } catch (InterruptedException e) {} latch.countDown(); // 任务完成,计数减一 }; for (int i = 0; i < 10; i++) { new Thread(task).start(); } latch.await(); // 主线程阻塞,直到所有任务完成 System.out.println("全部任务执行完毕");适用场景
多任务并行执行,最后统一汇总结果;主流程等待多个子任务全部完成。
第四章 面试高频总结
- Callable带返回值,FutureTask用来接收结果,get()阻塞等待。
- ReentrantLock是可重入锁,支持超时、公平锁、精准唤醒,需要手动解锁。
- 原子类基于CAS实现,无锁线程安全,适合高并发数值操作。
- 线程池7大参数、4种拒绝策略、执行流程是面试必考题。
- Semaphore控制最大并发数,CountDownLatch等待多任务全部完成。
第四章的内容就全部梳理完了,覆盖了PDF里所有工具类的定义、用法、比喻、场景和面试考点。确认理解后,我们进入第五章线程安全集合类的详细讲解。