说明:本次课程学习了 volatile 关键字(解决可见性 / 有序性、不能保证原子性、JVM 内存模型:工作内存 CPU 缓存 vs 主内存、可见性问题"程序永不退出"、volatile 三大语义:可见性 / 禁止重排序 / 不保证原子性)、Lock 锁(ReentrantLock 可重入锁)(相比 synchronized 的优势、unlock 必须放 finally 铁律、公平锁 / 非公平锁、tryLock / tryLock(timeout) / lockInterruptibly 避免死锁、synchronized vs Lock 七维对比)以及 线程池(ThreadPoolExecutor 的 7 个参数、执行流程:核心线程 → 工作队列 → 非核心线程 → 拒绝策略、线程复用 + 任务排队 + 动态扩容 + 上限保护、饭店餐馆类比、execute / shutdown)。
本作业共 3 道题:基础题(必做)——volatile 可见性"程序永不退出"复现与修复 + 计数器原子性对比;进阶题(必做)——ReentrantLock 可重入锁计数器(四种加锁用法);挑战题(选做)——线程池 ThreadPoolExecutor 任务提交与拒绝策略观察。覆盖今日核心知识点。
| 项目 | 要求 |
|---|---|
| 类名 | Demo_题号_你的姓名(如 Demo_01_张三、Demo_02_李四、Demo_03_王五) |
| 文件头注释 | 每个 Java 文件头部需包含 @author、@title、@create 文档注释 |
| 项目 | 要求 |
|---|---|
| 提交方式 | 将源码文件 + 运行结果截图提交至班级指定平台 |
| 截止时间 | 下次上课前 |
知识点:volatile 可见性(写 volatile 变量立即刷主存、读 volatile 变量强制从主存读)、JVM 内存模型(工作内存 CPU 缓存 vs 主内存、刷新有延迟)、可见性问题"程序永不退出"、volatile 不保证原子性(count++ 读-改-写 3 步依旧会被打断)、synchronized 保证原子性、方法引用 对象::方法 创建线程、线程数组 + join() 等待
场景描述:课堂用 VolatileDemo 演示了可见性问题——worker 线程 while(running) 空循环,main 线程修改 running 后 worker 读不到(读自己缓存旧值)导致程序永不退出,加 volatile 后正常退出;用 Counter 演示了 volatile 不保证原子性(1000 线程并发 count++ 结果小于 1000),只有加 synchronized 才保证原子性。请综合实现:① 可见性——用 volatile 解决"程序永不退出";② 原子性——对比 volatile 修饰 count 与 synchronized increment 的 1000 线程计数器结果。
题目要求:
/**
* @author 你的姓名
* @title 题目1:volatile 可见性 + 计数器原子性对比
* @create 2026/8/11
*/
public class Demo_01_姓名 {
// ========== ① 可见性部分:running 标志 ==========
// TODO 1: private volatile boolean running = true; // 加 volatile 解决可见性
// TODO 2: work() 方法——打印"work 线程开始......",while(running) 空循环,退出后打印"线程退出......"
// TODO 3: stop() 方法——把 running 改为 false
// ========== ② 原子性部分:计数器 ==========
// TODO 4: private volatile Integer count = 0; // 即使加 volatile,count++ 也不保证原子性
// TODO 5: public synchronized void increment() { count++; } // 加 synchronized 才保证原子性
// 注释标明 count++ 底层是 3 步:读 → 改 → 写
public static void main(String[] args) throws InterruptedException {
// ========== ③ 可见性演示 ==========
// TODO 6: 创建对象 vd,用方法引用 new Thread(vd::work, "worker") 启动
// TODO 7: 主线程 sleep(1000) 让 worker 先跑起来,再调用 vd.stop()
// 观察:不加 volatile 永不打印"线程退出......";加 volatile 正常退出
// ========== ④ 原子性演示 ==========
// TODO 8: 创建 Counter 对象,创建 1000 个线程并发执行 increment(),start()
// TODO 9: 用 for (Thread t : threads) t.join() 等待所有线程结束
// TODO 10: 打印最终 count,验证加 synchronized 后必然是 1000
}
}
预期输出示例(每次运行顺序可能不同):
work 线程开始......
work 线程退出...... ← 加了 volatile,worker 能看到 running 的修改,正常退出
最终 count=1000 ← 加了 synchronized,count 必然是 1000
要求说明:
running 必须加 volatile 修饰,work() 用 while(running) 空循环(循环体不要打印,才能复现可见性问题)new Thread(vd::work, "worker") 创建 worker 线程(对比 Lambda / 匿名内部类三种写法)count 必须加 volatile 修饰,increment() 加 synchronized 修饰(同步实例方法锁 this)join() 等待 1000 个线程全部结束再打印 count💡 提示:
🎯 挑战加分(选做):
running 的 volatile 去掉再运行,观察 worker 线程是否永远打印不出"线程退出......"(程序卡死)increment() 的 synchronized 去掉(保留 count 的 volatile)运行,观察 count 依然小于 1000——直接验证"volatile 不能保证原子性"AtomicInteger 代替 volatile + synchronized,观察 incrementAndGet() 是否无需加锁也能保证原子性(预习原子类)知识点:Lock 接口、ReentrantLock(可重入锁)、lock() / unlock() 基本用法、unlock 必须放 finally 铁律(否则死锁)、公平锁 / 非公平锁(new ReentrantLock() vs new ReentrantLock(true))、tryLock()(获取不到就算了,避免死锁)、tryLock(timeout, unit)(限时等待)、lockInterruptibly()(可中断获取锁)、synchronized vs Lock 七维对比、方法引用 ld::increment、10000 线程 + join() 验证
场景描述:课堂用 LockDemo 演示了 ReentrantLock 的四种用法——①基本用法 lock() + try-finally unlock ②tryLock() 尝试获取(拿不到返回 false)③tryLock(timeout) 限时等待 ④lockInterruptibly() 可中断获取。请实现一个计数器类:用 ReentrantLock 的四种加锁方式分别写 4 个方法,并用 10000 线程验证加锁后 count 正确,体会 unlock() 必须放 finally 的铁律。
题目要求:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 你的姓名
* @title 题目2:ReentrantLock 可重入锁四种用法计数器
* @create 2026/8/11
*/
public class Demo_02_姓名 {
private Integer count = 0;
// TODO 1: ReentrantLock lock = new ReentrantLock(); // 默认非公平锁
// 公平锁写法:new ReentrantLock(true); // 按申请顺序获取锁,性能略低
// ========== ① 基本用法:lock() + unlock(),unlock 必须放 finally ==========
public void increment() {
// TODO 2: lock.lock(); 加锁
// TODO 3: try { count++; } finally { lock.unlock(); } 释放锁必须放finally,否则死锁
}
// ========== ② tryLock():获取不到就算了,避免死锁 ==========
public boolean tryIncrement() {
// TODO 4: if (lock.tryLock()) { 立刻尝试,拿到返回true,否则false
// try { count++; return true; }
// finally { lock.unlock(); }
// }
// TODO 5: return false; 没抢到锁就返回false,继续干别的
return false;
}
// ========== ③ tryLock(timeout):限时等待 ==========
public boolean timedIncrement() {
// TODO 6: try {
// if (lock.tryLock(1, TimeUnit.SECONDS)) { // 最多等待1秒
// try { count++; return true; }
// finally { lock.unlock(); }
// }
// } catch (InterruptedException e) { throw new RuntimeException(e); }
// TODO 7: return false;
return false;
}
// ========== ④ lockInterruptibly():可中断获取锁 ==========
public void interruptLock() {
// TODO 8: try {
// lock.lockInterruptibly(); // 等待锁的过程中可被 interrupt() 打断
// try { count++; }
// finally { lock.unlock(); }
// } catch (InterruptedException e) {
// System.out.println("等待锁的过程中被中断");
// Thread.currentThread().interrupt(); // 重设中断标志
// }
}
public Integer getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
// ========== ⑤ 验证:10000 线程并发 increment() ==========
// TODO 9: 创建 LockCounter 对象,创建线程数组 Thread[10000],每个线程执行 对象::increment,start()
// TODO 10: for (Thread t : threads) t.join(); // 等待所有线程结束
// TODO 11: 打印最终 count,验证加了 Lock 锁后 count 必然是 10000
}
}
预期输出示例:
最终 count=10000 ← 用 lock + finally unlock 保护 count++,count 必然正确
要求说明:
increment()(lock/unlock)、tryIncrement()(tryLock)、timedIncrement()(tryLock(timeout))、interruptLock()(lockInterruptibly)try { ... } finally { lock.unlock(); } 释放锁——unlock 必须放 finallyjoin() 等待 10000 个线程结束再打印 count对象::increment 提交任务(对比 Lambda 写法)💡 提示:
unlock() 必须放 finally——否则代码抛异常或提前 return 时锁永不释放,其他线程永久阻塞(死锁)interrupt() 打断;catch 后 Thread.currentThread().interrupt() 重设中断标志(回顾 08-10 中断机制)new ReentrantLock() 非公平(释放后所有线程竞争,性能高);new ReentrantLock(true) 公平(按申请顺序,性能略低)🎯 挑战加分(选做):
increment() 的 finally { lock.unlock(); } 去掉(直接 count++ 后 unlock),模拟"提前 return 时锁不释放"的死锁场景,观察运行现象tryLock() / tryLock(1, SECONDS) / lockInterruptibly() 尝试拿锁,观察三种方法在"拿不到锁"时的不同表现lock.lock() → try { sell(); } finally { lock.unlock(); }),对比 synchronized 写法的差异(回顾七维对比)知识点:线程池(Thread Pool)(存储线程的容器、水池类比)、new Thread().start() 的三个问题(创建/销毁开销大、线程数量不受控、资源利用率低)、解决方案【线程复用 + 任务排队 + 动态扩容 + 上限保护】、ThreadPoolExecutor 的 7 个参数(corePoolSize / maximumPoolSize / keepAliveTime / unit / workQueue / threadFactory / handler)、执行流程(核心线程 → 工作队列 → 非核心线程 → 拒绝策略)、拒绝策略(AbortPolicy 抛 RejectedExecutionException)、execute() 提交任务、getActiveCount() / getQueue().size() / shutdown()、饭店餐馆类比
场景描述:课堂用 Demo04 演示了创建线程池(new ThreadPoolExecutor(3, 5, 60, SECONDS, ArrayBlockingQueue(10), defaultThreadFactory, AbortPolicy));用 ThreadPoolDemo 演示了 8 个任务提交到 2 核心 4 最大 队列 2 的线程池——2 个核心执行 + 2 个排队 + 2 个创建临时线程 + 2 个被拒绝(AbortPolicy)。请实现:创建自己的线程池(参数自选),提交 8 个耗时任务(每个 sleep 2 秒占住线程),用 getActiveCount() 和 getQueue().size() 观察线程池状态,并注释分析每个任务走了执行流程的哪一步(核心线程 / 工作队列 / 非核心线程 / 拒绝策略)。
题目要求:
import java.util.concurrent.*;
/**
* @author 你的姓名
* @title 题目3:线程池 ThreadPoolExecutor 任务提交与执行流程观察
* @create 2026/8/11
*/
public class Demo_03_姓名 {
public static void main(String[] args) {
// ========== ① 创建线程池 ==========
// TODO 1: new ThreadPoolExecutor(
// 2, // corePoolSize-核心线程数
// 4, // maximumPoolSize-最大线程数(核心 + 临时)
// 60, // keepAliveTime-非核心线程空闲存活时间
// TimeUnit.SECONDS, // unit-时间单位
// new ArrayBlockingQueue<>(2), // workQueue-工作队列 容量为2
// Executors.defaultThreadFactory(), // threadFactory-线程工厂
// new ThreadPoolExecutor.AbortPolicy() // handler-拒绝策略(默认抛异常)
// );
// ========== ② 连续提交 8 个耗时任务 ==========
// TODO 2: for (int i = 1; i <= 8; i++) {
// int taskId = i;
// try {
// pool.execute(() -> { // execute 把任务交给线程池(复用线程)
// String name = Thread.currentThread().getName();
// System.out.println(name + "执行任务" + taskId);
// try { Thread.sleep(2000); } // 模拟任务耗时,占住线程
// catch (InterruptedException e) { throw new RuntimeException(e); }
// });
// } catch (RejectedExecutionException e) {
// System.out.println("任务" + taskId + "被拒绝:" + e.getMessage());
// }
// }
// ========== ③ 观察线程池状态 ==========
// TODO 3: 打印 pool.getActiveCount() // 活动线程数(核心 + 临时)
// TODO 4: 打印 pool.getQueue().size() // 队列中排队任务数
// ========== ④ 关闭线程池 ==========
// TODO 5: pool.shutdown(); // 关闭线程池:不再接受新任务,已提交任务继续执行完
}
}
预期输出示例(每次运行顺序可能不同):
pool-1-thread-1执行任务1 ← 核心线程 1
pool-1-thread-2执行任务2 ← 核心线程 2
pool-1-thread-1执行任务3 ← 临时线程开始执行
pool-1-thread-2执行任务4
pool-1-thread-3执行任务5 ← 临时线程
pool-1-thread-4执行任务6
任务7被拒绝:... ← AbortPolicy 拒绝
任务8被拒绝:...
活动线程数:4 ← 2 核心 + 2 临时
队列中任务数:2 ← 排队等待的 2 个任务
要求说明:
RejectedExecutionExceptiongetActiveCount() 和 getQueue().size() 观察线程池状态pool.shutdown() 关闭线程池💡 提示:
pool.execute(runnable) 把任务交给线程池(复用线程,用 getName() 可看到多个任务共用同一线程名);new Thread().start() 每次创建新线程🎯 挑战加分(选做):
corePoolSize 改成 4、队列容量改成 4,重新提交 8 个任务,观察是否全部执行无人被拒(8 = 4 核心 + 4 排队)——体会 7 参数如何影响执行流程AbortPolicy 换成 CallerRunsPolicy 或 DiscardPolicy,提交超出容量的任务观察不同拒绝行为pool.submit(() -> {...}) 提交带返回值的任务(返回 Future),用 future.get() 获取结果(回顾 08-08 Callable + FutureTask)pool.shutdown() 注释掉再运行,观察程序是否一直不退出——体会 shutdown 关闭线程池的作用Demo_题号_姓名(如 Demo_01_张三、Demo_02_李四、Demo_03_王五)@author(你的姓名)、@title(题目名称)、@create 2026/8/11volatile / synchronized 关键字,题目 2 使用 java.util.concurrent.locks.ReentrantLock,题目 3 使用 java.util.concurrent.ThreadPoolExecutor,均为 JDK 自带,无需额外依赖)