Просмотр исходного кода

feat: 新增20260811课程内容——volatile关键字(VolatileDemo可见性程序永不退出)与Lock锁(LockDemo ReentrantLock可重入锁四种用法),死锁演示,线程池(ThreadPoolDemo ThreadPoolExecutor 7参数8任务演示),0810课后作业参考解答(homework0810卖票问题synchronized修复),含课后作业/课堂笔记/课堂练习

WanJL 2 недель назад
Родитель
Сommit
325a0ad058

+ 45 - 0
授课代码/c260811/src/course/Counter.java

@@ -0,0 +1,45 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Counter
+ * @description
+ * @create 2026/8/10
+ */
+public class Counter {
+    //使用volatile修饰也不能保证原子性
+    private volatile  Integer count=0;
+
+    //只要加了synchronized,就能够保证原子性
+    public synchronized void increment(){
+        count++;    //看似是1行,但底层是3步
+        //1、拿到当前count的值
+        //2、count+1
+        //3、自增后的值赋值给count
+    }
+
+    public Integer getCount() {
+        return count;
+    }
+
+    public static void main(String[] args) throws InterruptedException {
+        Counter counter=new Counter();
+        int threadCount=1000;
+
+        Thread[] threads=new Thread[threadCount];
+        for (int i = 0; i < threadCount; i++) {
+            threads[i]=new Thread(()->{
+                counter.increment();    //执行相加
+            });
+            threads[i].start();
+        }
+        //等待所有线程结束
+        for (Thread t:threads){
+            t.join();
+        }
+        //理论上应该是1000,但是实际上往往小于1000
+        System.out.println("最终 count="+counter.getCount());
+
+    }
+}

+ 52 - 0
授课代码/c260811/src/course/Demo01.java

@@ -0,0 +1,52 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo01
+ * @description  解决线程安全的可见性和有序性问题:volatile
+ * @create 2026/8/11
+ */
+public class Demo01 {
+    /*
+        volatile是用来解决可见性和有序性问题的,不能保证原子性。
+
+        可见性问题:一个线程对共享数据的修改,对其他线程不可见,导致其他线程无法做出相应的操作和响应。
+
+        volatile是可以解决程序永不退出的问题。
+        不加volatile可能导致程序永不退出。
+
+        可见性问题:程序永不退出:
+            代码看VolatileDemo.java
+
+        在JVM内存模型中,每个线程都有自己的独立的工作内存(其实就是CPU缓存),线程读取变量的时候会优先
+        从工作内存读,而不是再次读取真实的变量值。写变量的时候也是会先写入到工作内存,再刷新到主内存。
+        而且这个刷新也不是立即发生的,它是可能会有延迟的。
+        所以就可能出现:main线程把running属性改为了false并写入到了主内存。
+        但是worker线程一直读的都是自己的CPU缓存(工作内存)的旧值(true),导致while(running)永不退出,这就是可见性问题。
+
+        示意图 -mermaid:
+        flowchart TB
+            subgraph Core1[CPU 核心 1 —— worker 线程]
+                Cache1[L1/L2 缓存<br/>running = true(旧值缓存)]
+            end
+            subgraph Core2[CPU 核心 2 —— main 线程]
+                Cache2[L1/L2 缓存<br/>running = false]
+            end
+            MainMemory[(主内存<br/>running = false)]
+
+            Cache1 -.需要及时刷新.-> MainMemory
+            Cache2 -.写操作会立即刷回.-> MainMemory
+
+            note1[加 volatile 后:<br/>写 volatile 变量立即刷主存,<br/>读 volatile 变量强制从主存读<br/>= 禁止使用线程本地缓存]
+
+
+
+        volatile语义:
+            1、可见性:写volatile修饰的变量,会立刻刷新到主内存,读volatile变量会强制从主内存读,禁止使用CPU缓存。
+            2、禁止重排序(有序性):一般情况下编译器和CPU会对代码指令进行重排序以方便运行,但是如果使用了volatile,编译器和CPU就不会把volatile的读写操作以及它前后的指令进行指令重排。
+            3、不保证原子性:volatile count++,它依旧会丢失更新,做不到让3步变为1步。必须要synchronized/lock/原子类
+
+     */
+
+}

+ 51 - 0
授课代码/c260811/src/course/Demo02.java

@@ -0,0 +1,51 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo02
+ * @description Lock锁
+ * @create 2026/8/11
+ */
+public class Demo02 {
+    /*
+        从JDK1.5开始,Java就提供了java.util.concurrent.locks.Lock接口以及它的实现类ReentrantLock(可重入锁)。
+        相比于synchronized,Lock提供了更精细的锁控制,比如可以进行超时等待(拿不到锁了不会再傻等),可以响应中断(等等锁的过程中可以被interrupt()进行中断)
+        可以指定公平性(可以按照申请顺序获取锁),而且还支持多个条件变量(可以精准的唤醒指定的线程)
+
+        但是使用Lock必须要记住一条铁律:
+            synchronized是自动释放锁的(在方法结束或出现异常的时候)。
+            但是Lock必须要手动释放锁,而且unlock() 释放锁的方法必须要放到finally块中,否则一旦代码抛异常或提前return,锁就永远不会被释放了,其他线程也会永远阻塞(死锁)
+
+        public ReentrantLock() 创建一个可重入锁对象,默认是非公平锁,释放后所有线程竞争锁资源。
+        public ReentrantLock(boolean fair)  创建一个可重入锁对象,如果fair是true,则创建公平锁,按照申请顺序获取锁,性能略低
+        public void lock() 加锁
+        public void unlock() 释放锁
+        public boolean tryLock() 尝试获取锁,获取不到就算了
+        public boolean tryLock(long timeout, TimeUnit unit) 尝试获取锁 并设置等待时间和时间单位
+        public void lockInterruptibly() 设置可中断的锁
+
+        语法简洁度:
+            synchronized--更简单,只需要关键字就可以了
+            ReentrantLock--比较复杂,需要try-finally块
+        锁释放:
+            synchronized--自动释放(方法结束、出现异常都会自动释放锁)
+            ReentrantLock--必须要手动释放unlock()
+        是否可中断:
+            synchronized--不可中断不支持中断
+            ReentrantLock--支持中断,lockInterruptibly()可以设置可中断锁
+        是否可以超时等待:
+            synchronized--不支持超时等待
+            ReentrantLock--支持超时等待,tryLock(timeout)
+        是否支持锁读:
+            synchronized--不支持
+            ReentrantLock--是属于读写分离,单独上锁
+        底层实现:
+            synchronized--  监视器锁(Monitor Lock)
+            ReentrantLock-- AQS (AbstractQueuedSynchronizer)抽象队列同步器
+        JDK21版本更新:
+            synchronized-- 进行了大幅度的优化
+            ReentrantLock-- 性能相近
+     */
+
+}

+ 156 - 0
授课代码/c260811/src/course/Demo03.java

@@ -0,0 +1,156 @@
+package course;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo03
+ * @description 死锁
+ * @create 2026/8/11
+ */
+public class Demo03 {
+    /*
+        线程死锁指的是由两个或多个线程相互持有对方所需要的资源,导致这些线程处在等待状态,无法继续执行。
+        什么情况会出现死锁?
+            资源有限
+            同步嵌套
+
+        出现死锁的条件:
+            1、互斥    资源同一时刻只能被同一个线程占用,  锁A/锁B只能被一个线程持有
+            2、持有且等待 线程持有一个资源,同时等待另一个资源  线程1持有锁A,还在等锁B
+            3、不可剥夺 已经持有的资源不能被强行夺走,只能自己释放,锁必须有持有者主动释放
+            4、循环等待  多个线程形成了 你等我,我等你的环, 线程1等锁B,线程2等锁A
+
+        怎么避免死锁?
+            1、固定锁顺序,所有的线程都按照相同的顺序进行加锁
+            2、tryLock() 超时获取,拿不到就放弃,而不是无限等待。
+            3、使用更加高级的并发工具,避免手动加多把锁,比如使用一些同步锁的API 比如同步Map集合-->ConcurrentHashMap
+
+     */
+    private static final Object lockA = new Object();
+    private static final Object lockB = new Object();
+
+    /* 死锁
+       public static void main(String[] args) {
+           //线程1 先拿lockA,再拿lockB
+           Thread t1=new Thread(()->{
+               synchronized (lockA){
+                   System.out.println("线程1 拿到 lockA");
+                   try {
+                       Thread.sleep(100);
+                   } catch (InterruptedException e) {
+                       throw new RuntimeException(e);
+                   }
+                   synchronized (lockB){   //等待lockB(被线程2持有)
+                       System.out.println("线程1 拿到LockB");
+                   }
+               }
+           },"线程1");
+
+           Thread t2=new Thread(()->{
+               synchronized (lockB){
+                   System.out.println("线程2 拿到 lockB");
+                   try {
+                       Thread.sleep(10000);
+                   } catch (InterruptedException e) {
+                       throw new RuntimeException(e);
+                   }
+                   synchronized (lockA){   //等待lockA(被线程1持有)
+                       System.out.println("线程2 拿到LockA");
+                   }
+               }
+           },"线程2");
+
+           t1.start();
+           t2.start();
+
+
+
+       }
+
+     */
+    /*固定锁顺序,所有的线程都按照相同的顺序进行加锁
+    public static void main(String[] args) {
+        Runnable task=()->{
+            synchronized (lockA){
+                System.out.println(Thread.currentThread().getName()+" 拿到 lockA");
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                synchronized (lockB){   //等待lockB(被线程2持有)
+                    System.out.println(Thread.currentThread().getName()+" 拿到 LockB");
+                }
+            }
+        };
+        Thread t1=new Thread(task,"线程1");
+        Thread t2=new Thread(task,"线程2");
+        t1.start();
+        t2.start();
+    }
+    */
+    /*tryLock() 超时获取,拿不到就放弃,而不是无限等待。
+
+    private static final ReentrantLock rLockA = new ReentrantLock();
+    private static final ReentrantLock rLockB = new ReentrantLock();
+    public static void main(String[] args) {
+        //线程1 先拿lockA,再拿lockB
+        Thread t1=new Thread(()->{
+            try {
+                if(rLockA.tryLock(1, TimeUnit.SECONDS)){
+                    try {
+                        System.out.println("线程1 拿到 lockA");
+                        Thread.sleep(100);
+                        if(rLockB.tryLock(1, TimeUnit.SECONDS)){   //等待lockB(被线程2持有),等待1秒钟,超时了就放弃
+                            try {
+                                System.out.println("线程1 拿到LockB");
+                            }finally {
+                                rLockB.unlock();    //释放锁
+                            }
+                        }else {
+                            System.out.println("线程1 拿不到锁");
+                        }
+                    } finally {
+                        rLockA.unlock();    //释放锁
+                    }
+                }
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        },"线程1");
+
+        Thread t2=new Thread(()->{
+            try {
+                if(rLockB.tryLock(1, TimeUnit.SECONDS)){
+                    try {
+                        System.out.println("线程2 拿到 lockB");
+                        Thread.sleep(100);
+                        if(rLockA.tryLock(1, TimeUnit.SECONDS)){   //等待lockA(被线程1持有),等待1秒钟,超时了就放弃
+                            try{
+                                System.out.println("线程2 拿到LockA");
+                            }finally {
+                                rLockA.unlock();    //释放锁
+                            }
+                        }else {
+                            System.out.println("线程2 拿不到锁");
+                        }
+                    }finally {
+                        rLockB.unlock();
+                    }
+                }
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        },"线程2");
+
+        t1.start();
+        t2.start();
+
+
+    }
+*/
+}

+ 69 - 0
授课代码/c260811/src/course/Demo04.java

@@ -0,0 +1,69 @@
+package course;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo04
+ * @description 线程池
+ * @create 2026/8/11
+ */
+public class Demo04 {
+    /*
+        说到池,第一想到是水池,水池是个容器,存储水的。
+        线程池也是一种容器,存储线程的。
+        每次都使用start() 创建线程,然后运行然后销毁线程。创建线程的过程的成本是比较高的,涉及到和底层操作系统的交互。
+        当程序中需要创建大量的生存期非常短的线程,频繁的线程创建和销毁就会耗费大量的资源。
+        所以就有了线程池
+        new Tread().star() 会带来三个问题:
+            1、创建/销毁线程的开销很大:每个线程都要分配1MB的栈内存资源,还有和底层操作系统进行交互,频繁的创建和销毁会拖垮性能。
+            2、线程的数量不受控:高并发的时候可能会瞬间创建成千上万个线程,耗尽内存和CPU,导致系统崩溃。
+            3、资源利用率低:可能一个任务只需要执行几毫秒,但是创建/销毁需要几秒线程的开销比业务本身还要大。
+
+        线程池(Thread Pool)的解决方案:提前创建一批线程并且常驻。任务来了直接复用空闲的线程,任务过多的时候排队等待。
+        排队也排不下再进行动态扩容。达到了上限后执行拒绝。
+        【线程复用+任务排队+动态扩容+上限保护】
+
+        类比-饭店餐馆:
+            核心线程数:饭店里面摆好的几张餐桌,比如5个,不管忙不忙都在。
+            最大线程数:饭店额外存了几张临时餐桌,比如3个,当5个满的时候用临时的。
+            工作队列:饭店门口摆了5个小凳,当8个桌子都满了的时候,顾客就坐在小凳上排队等待。
+            空闲存活时间:超过某个时间点3个临时餐桌没人用就收回来。
+            拒绝策略:如果5+3+5都满了,服务员就拒绝。
+
+        corePoolSize        核心线程数               即使是空闲也要保留
+        maximumPoolSize     最大线程数               必须要大于核心线程数
+        keepAliveTime       非核心线程空闲存活时间     默认只对非核心线程有效
+        unit                时间单位                存活时间的单位
+        workQueue           工作队列                排队的地方
+        threadFactory       线程工厂                创建线程的工厂
+        handler             拒绝策略                如果排不下了怎么办
+
+
+        线程池的执行流程:
+            核心线程-->工作队列-->非核心线程-->拒绝策略
+        线程不够了,不会第一时间创建新的线程,而是先把任务放在工作队列,只有当工作队列满了,并且线程数还没到最大线程数的时候,才会创建新的线程。
+
+
+     */
+    public static void main(String[] args) {
+        ThreadPoolExecutor pool = new ThreadPoolExecutor(
+                3,  //corePoolSize-核心线程数 创建线程池的时候就先创建3个核心的线程,不管有没有任务使用,都留着。
+                5,  //maximumPoolSize-最大线程数     核心线程数 + 临时线程数
+                60,//keepAliveTime-非核心线程空闲存活时间
+                TimeUnit.SECONDS,    //unit-时间单位
+                new ArrayBlockingQueue<>(10),   //workQueue-工作队列 容量为10
+                Executors.defaultThreadFactory(),    //threadFactory-线程工厂,可以自定义线程名、是否为守护线程
+                new ThreadPoolExecutor.AbortPolicy()    //handler-拒绝策略--AbortPolicy默认策略抛异常
+        );
+
+
+
+
+    }
+
+}

+ 95 - 0
授课代码/c260811/src/course/LockDemo.java

@@ -0,0 +1,95 @@
+package course;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title LockDemo
+ * @description
+ * @create 2026/8/11
+ */
+public class LockDemo {
+    private Integer count=0;
+    //创建锁对象---默认是非公平锁
+    ReentrantLock lock=new ReentrantLock();
+    //new ReentrantLock(true);  公平锁,按照申请顺序获取锁,性能略低
+
+    //---------基本用法:lock()+unlock() ,前提是必须要finally
+    public void increment(){
+        //加锁-- lock()
+        lock.lock();
+        try{
+            count++;
+        }finally {// 释放锁,必须要释放,否则其他线程永远拿不到锁
+            lock.unlock();
+        }
+    }
+    //--------进阶用法:tryLock()尝试获取锁,如果实在拿不到就算了,继续干别的。避免死锁的手段。
+    public boolean tryIncrement(){
+        if (lock.tryLock()){    //立刻尝试获取锁,获取到了就返回true,否则返回false
+            try {
+                count++;
+                return true;
+            }finally {
+                lock.unlock();
+            }
+        }
+        return false;   //没抢到锁就返回false
+    }
+    //--------进阶用法:tryLock(timeout)限时等待
+    public boolean timedIncrement(){
+        try {
+            if (lock.tryLock(1, TimeUnit.SECONDS)){ //最多等待1秒
+                try {
+                    count++;
+                    return true;
+                }finally {
+                    lock.unlock();  //释放锁
+                }
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        return false;
+    }
+    //--------进阶用法:可中断的获取锁(和sleep中断机制配合)
+    public void interruptLock(){
+        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 {
+        LockDemo ld=new LockDemo();
+        Thread[] threads=new Thread[10000];
+        for (int i = 0; i < threads.length; i++) {
+            threads[i]= new Thread(ld::interruptLock);
+            threads[i].start();
+        }
+        //等待所有线程结束
+        for (Thread t:threads){
+            t.join();
+        }
+
+        //理论上应该是1000,但是实际上往往小于1000
+        System.out.println("最终 count="+ld.getCount());
+    }
+}

+ 43 - 0
授课代码/c260811/src/course/ThreadPoolDemo.java

@@ -0,0 +1,43 @@
+package course;
+
+import java.util.concurrent.*;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title ThreadPoolDemo
+ * @description
+ * @create 2026/8/11
+ */
+public class ThreadPoolDemo {
+    public static void main(String[] args) {
+        ThreadPoolExecutor pool=
+                new ThreadPoolExecutor(2,4,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
+
+        for (int i = 1; i <=8; i++) {
+            int taskId=i;
+            try{
+                pool.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());
+            }
+        }
+
+        //观察线程池的状态:活动线程数、队列的大小
+        System.out.println("活动线程数:"+pool.getActiveCount());
+        System.out.println("队列中任务数:"+pool.getQueue().size());
+
+        pool.shutdown();    //关闭线程池
+
+    }
+}

+ 58 - 0
授课代码/c260811/src/course/VolatileDemo.java

@@ -0,0 +1,58 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title VolatileDemo
+ * @description
+ * @create 2026/8/11
+ */
+public class VolatileDemo {
+    //如果不加volatile,其他线程可能永远都看不到修改
+    private volatile boolean running=true;
+
+    /**
+     * 线程工作的方法
+     */
+    public void work(){
+        System.out.println("work 线程开始......");
+        while (running){
+            //空循环,什么都不做....
+            //System.out.println("work....");
+        }
+        System.out.println("线程退出......");
+    }
+
+    /**
+     * 停止线程工作的方法
+     */
+    public void stop(){
+        running=false;
+    }
+
+    public static void main(String[] args) throws InterruptedException {
+        VolatileDemo vd=new VolatileDemo();
+        Thread t=new Thread(vd::work,"worker");
+        /*
+        相当于:
+        new Thread(()->{
+            vd.work();
+        },"worker")
+        相当于:
+        new Thread(new Runnable(){
+            @Override
+            public void run(){
+                vd.work();
+            }
+        },"worker")
+         */
+
+        t.start();
+        Thread.sleep(1000);
+
+        //主线程停止vd对象的工作
+        vd.stop();
+
+
+    }
+}

+ 56 - 0
授课代码/c260811/src/homework0810/Demo_03.java

@@ -0,0 +1,56 @@
+package homework0810;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo_03
+ * @description
+ * @create 2026/8/11
+ */
+public class Demo_03 {
+    // ========== ① 卖票类:实现 Runnable ==========
+    static class SellTicket implements Runnable {
+        private int tickets = 100;   // 一共 100 张票
+
+        // 写法1:同步方法(锁 this)—— TODO 2 完成后给方法加 synchronized
+        public void sell() {
+            // TODO 1: if (tickets > 0) { sleep(10); 打印 "xxx正在出售第 tickets 张票"; tickets--; }
+            if (tickets > 0) {
+                try {
+                    Thread.sleep(10);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                System.out.println(Thread.currentThread().getName()+"正在出售第 "+tickets+" 张票");
+                tickets--;
+            }
+        }
+
+        @Override
+        public void run() {
+            while (tickets > 0) {
+                synchronized (this){
+                    sell();   // TODO 2: 先不加 synchronized 运行复现 Bug,再加 synchronized 修复
+                }
+            }
+        }
+    }
+
+    public static void main(String[] args) throws InterruptedException {
+        // ========== ② 创建 3 个售票窗口线程(共用同一个 SellTicket 对象) ==========
+        // TODO 3: new Thread(new SellTicket(), "窗口1") / "窗口2" / "窗口3",start() 启动
+        SellTicket st=new SellTicket();
+
+        //创建三个线程对象,模拟3个售票窗口
+        Thread t1=new Thread(st,"窗口1");
+        Thread t2=new Thread(st,"窗口2");
+        Thread t3=new Thread(st,"窗口3");
+
+        t1.start();
+        t2.start();
+        t3.start();
+        // TODO 4: 用线程数组 + join() 等待所有线程卖票结束
+
+        // TODO 5: 运行多次观察:无 synchronized 时出现相同票 / 负数票;加 synchronized 后消失
+    }
+}

+ 304 - 0
课后作业/2026年8月11日-作业.md

@@ -0,0 +1,304 @@
+# 2026年8月11日 课后作业 — volatile 关键字、Lock 锁(可重入锁)与线程池
+
+> **说明**:本次课程学习了 **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` 文档注释 |
+
+### 二、提交方式与截止时间
+
+| 项目 | 要求 |
+|------|------|
+| **提交方式** | 将源码文件 + 运行结果截图提交至班级指定平台 |
+| **截止时间** | 下次上课前 |
+
+---
+
+## 一、基础题(必做)
+
+### 题目 1:volatile 可见性与原子性 —— 程序永不退出 + 计数器对比 VolatileCounterDemo(⭐⭐ 必做)
+
+**知识点**:`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 线程计数器结果。
+
+**题目要求**:
+
+```java
+/**
+ * @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
+```
+
+**要求说明**:
+1. `running` 必须加 `volatile` 修饰,`work()` 用 `while(running)` 空循环(循环体不要打印,才能复现可见性问题)
+2. 必须用方法引用 `new Thread(vd::work, "worker")` 创建 worker 线程(对比 Lambda / 匿名内部类三种写法)
+3. `count` 必须加 `volatile` 修饰,`increment()` 加 `synchronized` 修饰(同步实例方法锁 this)
+4. 必须用**线程数组 + `join()`** 等待 1000 个线程全部结束再打印 count
+5. 在代码注释中写出你的结论:**volatile 解决什么问题?不能解决什么问题?**
+
+**💡 提示**:
+- **可见性问题根源**:每个线程有自己的工作内存(CPU 缓存),读变量优先从工作内存读、写变量先写工作内存再刷主存(**有延迟**)——worker 一直读缓存里的旧值 true
+- **volatile 三大语义**:①可见性(写立即刷主存、读强制从主存读)②禁止重排序(有序性)③**不保证原子性**
+- **count++ 非原子**:表面一行代码,底层 3 步(读 → 改 → 写);volatile 无法把 3 步变 1 步,多个线程读旧值计算互相覆盖 → 结果小于 1000
+- **synchronized 保证原子性**:同一时刻只有一个线程能执行 count++,其他线程排队等待 → count 必然是 1000
+
+**🎯 挑战加分(选做)**:
+1. 把 `running` 的 volatile 去掉再运行,观察 worker 线程是否**永远打印不出"线程退出......"**(程序卡死)
+2. 把 `increment()` 的 synchronized 去掉(保留 count 的 volatile)运行,观察 count **依然小于 1000**——直接验证"volatile 不能保证原子性"
+3. 改用 `AtomicInteger` 代替 volatile + synchronized,观察 `incrementAndGet()` 是否无需加锁也能保证原子性(预习原子类)
+
+---
+
+## 二、进阶题(必做)
+
+### 题目 2:ReentrantLock 可重入锁 —— 四种加锁用法计数器 LockCounterDemo(⭐⭐⭐ 必做)
+
+**知识点**:`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 的铁律。
+
+**题目要求**:
+
+```java
+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 必然正确
+```
+
+**要求说明**:
+1. **四种加锁方法都必须实现**:`increment()`(lock/unlock)、`tryIncrement()`(tryLock)、`timedIncrement()`(tryLock(timeout))、`interruptLock()`(lockInterruptibly)
+2. 每个拿到锁的方法都必须用 `try { ... } finally { lock.unlock(); }` 释放锁——**unlock 必须放 finally**
+3. main 中必须用**线程数组 + `join()`** 等待 10000 个线程结束再打印 count
+4. 必须用**方法引用** `对象::increment` 提交任务(对比 Lambda 写法)
+5. 在代码注释中写出结论:**unlock 为什么要放 finally?如果 count++ 抛异常会发生什么?**
+
+**💡 提示**:
+- **unlock 放 finally 铁律**:synchronized 是自动释放锁的;Lock **必须手动释放**,`unlock()` **必须放 finally**——否则代码抛异常或提前 return 时锁永不释放,其他线程永久阻塞(**死锁**)
+- **tryLock() 避免死锁**:获取不到就返回 false,线程继续干别的,不阻塞等待(打破"持有且等待")
+- **tryLock(timeout)**:最多等指定时间,超时返回 false——"拿不到锁不会再傻等"
+- **lockInterruptibly()**:等待锁的过程中可被 `interrupt()` 打断;catch 后 `Thread.currentThread().interrupt()` 重设中断标志(回顾 08-10 中断机制)
+- **公平锁 vs 非公平锁**:`new ReentrantLock()` 非公平(释放后所有线程竞争,性能高);`new ReentrantLock(true)` 公平(按申请顺序,性能略低)
+- **七维对比**:synchronized vs Lock——语法简洁度 / 锁释放 / 可中断 / 超时等待 / 锁读(读写分离)/ 底层实现(Monitor Lock vs AQS)/ JDK21 版本更新(性能相近)
+
+**🎯 挑战加分(选做)**:
+1. 把 `increment()` 的 `finally { lock.unlock(); }` 去掉(直接 count++ 后 unlock),模拟"提前 return 时锁不释放"的死锁场景,观察运行现象
+2. 写两个线程:一个持有锁不放(sleep 5 秒),另一个分别用 `tryLock()` / `tryLock(1, SECONDS)` / `lockInterruptibly()` 尝试拿锁,观察三种方法在"拿不到锁"时的不同表现
+3. 用 ReentrantLock 改造 08-10 的卖票问题(`lock.lock()` → try { sell(); } finally { lock.unlock(); }),对比 synchronized 写法的差异(回顾七维对比)
+
+---
+
+## 三、挑战题(选做)
+
+### 题目 3:线程池 —— ThreadPoolExecutor 任务提交与执行流程观察 ThreadPoolTaskDemo(⭐⭐⭐⭐ 选做)
+
+**知识点**:**线程池(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()` 观察线程池状态,并**注释分析每个任务走了执行流程的哪一步**(核心线程 / 工作队列 / 非核心线程 / 拒绝策略)。
+
+**题目要求**:
+
+```java
+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 个任务
+```
+
+**要求说明**:
+1. **7 个参数必须完整填写**,并在每个参数后加注释说明含义(corePoolSize / maximumPoolSize / keepAliveTime / unit / workQueue / threadFactory / handler)
+2. 必须提交 **8 个任务**(每个 sleep 2 秒模拟耗时),用 try-catch 捕获 `RejectedExecutionException`
+3. 必须打印 `getActiveCount()` 和 `getQueue().size()` 观察线程池状态
+4. 必须调用 `pool.shutdown()` 关闭线程池
+5. 在代码注释中**逐个分析 8 个任务各自走了执行流程的哪一步**(核心线程执行 / 工作队列排队 / 创建临时线程 / 拒绝策略拒绝),写出你的分析结论
+
+**💡 提示**:
+- **线程池执行流程**:**核心线程 → 工作队列 → 非核心线程 → 拒绝策略**——线程不够了**不会第一时间创建新线程**,而是先把任务放工作队列;只有**队列满了**且线程数**还没到最大线程数**时才创建临时线程
+- **8 任务走向分析**(2 核心 4 最大 队列 2):任务 1、2 → 核心线程执行;任务 3、4 → 工作队列排队;任务 5、6 → 队列满 → 创建临时线程;任务 7、8 → 线程满 + 队列满 → AbortPolicy 拒绝(抛 RejectedExecutionException)
+- **getActiveCount()**:当前活动线程数(这里 2 核心 + 2 临时 = 4);**getQueue().size()**:队列中排队任务数(这里 2 个)
+- **execute vs start**:`pool.execute(runnable)` 把任务交给线程池(**复用线程**,用 getName() 可看到多个任务共用同一线程名);`new Thread().start()` 每次创建新线程
+- **shutdown()**:关闭线程池——不再接受新任务,已提交任务继续执行完;不 shutdown 程序可能一直不退出(池中非守护线程还活着)
+- **拒绝策略四种**:AbortPolicy(默认,抛异常)/ CallerRunsPolicy(调用者运行)/ DiscardPolicy(静默丢弃)/ DiscardOldestPolicy(丢弃最旧任务)
+
+**🎯 挑战加分(选做)**:
+1. 修改参数:`corePoolSize` 改成 4、队列容量改成 4,重新提交 8 个任务,观察是否全部执行无人被拒(8 = 4 核心 + 4 排队)——体会 7 参数如何影响执行流程
+2. 把 `AbortPolicy` 换成 `CallerRunsPolicy` 或 `DiscardPolicy`,提交超出容量的任务观察不同拒绝行为
+3. 用 `pool.submit(() -> {...})` 提交**带返回值**的任务(返回 Future),用 `future.get()` 获取结果(回顾 08-08 Callable + FutureTask)
+4. 把 `pool.shutdown()` 注释掉再运行,观察程序是否**一直不退出**——体会 shutdown 关闭线程池的作用
+
+---
+
+## 四、提交要求
+
+- **类名**:`Demo_题号_姓名`(如 `Demo_01_张三`、`Demo_02_李四`、`Demo_03_王五`)
+- **文件头注释**:每个 Java 文件头部必须包含 `@author`(你的姓名)、`@title`(题目名称)、`@create 2026/8/11`
+- **提交内容**:源码文件 + 运行结果截图
+- **必做/选做**:基础题(题目 1)、进阶题(题目 2)为**必做**;挑战题(题目 3)为**选做**,鼓励学有余力的同学完成
+- **运行环境**:JDK 8 及以上(题目 1 使用 `volatile` / `synchronized` 关键字,题目 2 使用 `java.util.concurrent.locks.ReentrantLock`,题目 3 使用 `java.util.concurrent.ThreadPoolExecutor`,均为 JDK 自带,无需额外依赖)
+

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
课堂笔记/01-JavaSE基础-笔记/20260811-笔记.md


+ 611 - 0
课堂练习题/20260811-volatile与Lock锁练习.md

@@ -0,0 +1,611 @@
+# 2026-08-11 volatile 与 Lock 锁练习(课堂练习用)
+
+> **说明**:练习今日新学知识点——**volatile 关键字**(解决可见性 / 有序性、不能保证原子性、JVM 内存模型:工作内存 CPU 缓存 vs 主内存、可见性问题"程序永不退出"、volatile 三大语义)、**线程安全三大特性解决方案**(原子性→synchronized/Lock/Atomic、可见性→volatile/synchronized/Lock、有序性→volatile/synchronized)、**计数器问题升级**(volatile 修饰 count 不能保证原子性、synchronized 保证原子性、1000 线程验证)、**Lock 锁(ReentrantLock 可重入锁)**(lock() / unlock() 基本用法、unlock 必须放 finally 铁律、tryLock() / tryLock(timeout) / lockInterruptibly() 避免死锁、公平锁 / 非公平锁、synchronized vs Lock 七维对比)。
+
+---
+
+## 练习一:volatile 可见性 —— "程序永不退出"复现与修复
+
+**难度**:⭐⭐⭐
+**知识点**:`volatile` 可见性、JVM 内存模型(工作内存 CPU 缓存 vs 主内存)、`while(running)` 空循环、方法引用 `对象::方法` 创建线程、`Thread.sleep()`
+
+**场景描述**:课堂学了 **volatile 的可见性语义**:每个线程有独立的工作内存(CPU 缓存),读变量优先从工作内存读,写变量先写工作内存再刷主存(有延迟)。不加 volatile 时,main 线程把 `running` 改成 false 写入了主内存,但 worker 线程一直读自己缓存里的旧值 true,导致 `while(running)` 永不退出;加了 `volatile` 后,读 volatile 变量强制从主存读,worker 能立刻看到修改正常退出。
+
+### 案例需求
+
+复现并修复"程序永不退出":worker 线程 `while(running)` 空循环,main 线程 sleep 后调用 `stop()` 修改 running,验证加/不加 volatile 的差异。
+
+### 实现步骤
+
+1. 定义一个类,成员变量 `private volatile boolean running = true;`
+2. `work()` 方法:打印"开始",进入 `while(running)` 空循环(什么都不做),退出后打印"线程退出"
+3. `stop()` 方法:把 `running` 改为 false
+4. main 中用方法引用 `new Thread(vd::work, "worker")` 创建并启动线程
+5. main 线程 `sleep(1000)` 让 worker 先跑起来,再调用 `vd.stop()`
+6. 观察运行结果:**先去掉 volatile 复现永不退出,再加 volatile 修复**
+
+### 代码框架
+
+```java
+public class VolatileVisibilityTest {
+    //如果不加volatile,其他线程可能永远都看不到修改
+    private volatile boolean running = true;
+
+    // TODO 1: work() 方法——打印"work 线程开始......",while(running) 空循环,退出后打印"线程退出......"
+
+    // TODO 2: stop() 方法——把 running 改为 false
+
+    public static void main(String[] args) throws InterruptedException {
+        VolatileVisibilityTest vd = new VolatileVisibilityTest();
+
+        // TODO 3: 用方法引用创建线程:new Thread(vd::work, "worker"),start() 启动
+        // 等价于 new Thread(() -> vd.work(), "worker")
+        // 等价于 new Thread(new Runnable(){ public void run(){ vd.work(); } }, "worker")
+
+        // TODO 4: 主线程 Thread.sleep(1000) 让 worker 先跑起来
+
+        // TODO 5: 主线程调用 vd.stop() 修改 running
+    }
+}
+```
+
+**输出示例**:
+```
+work 线程开始......
+        ← 若无 volatile:永不打印"线程退出......"(程序卡死)
+work 线程退出......   ← 加了 volatile:能正常退出
+```
+
+**选做加分**:
+- 把 `running` 的 volatile 去掉再运行,观察 worker 线程是否**永远打印不出"线程退出......"**(程序卡死)
+- 在 while 循环里加上 `System.out.println("work....")` 再运行,观察输出是否出现——体会空循环 vs 带输出对缓存可见性的影响
+- 把 `vd::work` 改成 Lambda `() -> vd.work()` 和匿名内部类两种写法,对比三种创建线程的等价性
+
+**思考题**:
+1. 为什么 main 线程修改了 `running`,worker 线程却看不到?(提示:每个线程有自己的工作内存 CPU 缓存,worker 一直读缓存里的旧值 true,刷新到主内存有延迟)
+2. `volatile` 解决了什么问题?(提示:可见性——写 volatile 变量立即刷主存、读 volatile 变量强制从主存读)
+3. `volatile` 能保证原子性吗?(提示:不能!`volatile count++` 依旧会丢失更新)
+
+---
+
+## 练习二:计数器问题升级 —— volatile 不保证原子性 vs synchronized 保证原子性
+
+**难度**:⭐⭐⭐⭐
+**知识点**:`volatile` 不保证原子性、`synchronized` 保证原子性、`count++` 读-改-写 3 步、线程数组 + `join()` 等待、Lambda 创建 Runnable
+
+**场景描述**:课堂学了**计数器问题升级**:上节课(08-10)Counter 用普通 Integer count,1000 线程并发 count++ 结果小于 1000;这节课给 count 加了 **volatile** 修饰,但**结果照样小于 1000**——证明 volatile 只能保证可见性、**不能保证原子性**(count++ 的"读-改-写"3 步依旧可能被打断)。只有加 **synchronized** 的 increment() 才能保证原子性,count **必然是 1000**。
+
+### 案例需求
+
+创建 Counter 类:`volatile Integer count` + `synchronized increment()`,用 1000 线程并发验证:加 synchronized 后 count 必然 1000。
+
+### 实现步骤
+
+1. Counter 类:`private volatile Integer count = 0;`
+2. `increment()` 方法:`count++`(注释标明底层是读-改-写 3 步)
+3. 给 increment() 加 `synchronized` 修饰(同步实例方法,锁 this)
+4. main:创建 Counter,1000 个线程并发调用 increment()
+5. 用线程数组 + `join()` 等待所有线程结束
+6. 打印最终 count,验证必然是 1000
+
+### 代码框架
+
+```java
+public class CounterAtomicityTest {
+    //使用volatile修饰也不能保证原子性
+    private volatile Integer count = 0;
+
+    //只要加了synchronized,就能够保证原子性
+    // TODO 1: 给 increment() 加 synchronized 修饰
+    public void increment() {
+        count++;    //看似是1行,但底层是3步
+        //1、拿到当前count的值
+        //2、count+1
+        //3、自增后的值赋值给count
+    }
+
+    public Integer getCount() {
+        return count;
+    }
+
+    public static void main(String[] args) throws InterruptedException {
+        CounterAtomicityTest counter = new CounterAtomicityTest();
+        int threadCount = 1000;
+
+        // TODO 2: 创建线程数组 Thread[threadCount],每个线程执行 counter.increment(),start()
+        Thread[] threads = new Thread[threadCount];
+        for (int i = 0; i < threadCount; i++) {
+            threads[i] = new Thread(() -> {
+                counter.increment();    //执行相加
+            });
+            threads[i].start();
+        }
+
+        // TODO 3: 等待所有线程结束 for (Thread t : threads) t.join();
+
+        //理论上应该是1000,加上synchronized后必然是1000
+        System.out.println("最终 count=" + counter.getCount());
+    }
+}
+```
+
+**输出示例**:
+```
+最终 count=1000    ← 加了 synchronized,必然是 1000
+```
+
+**选做加分**:
+- 把 increment() 的 `synchronized` 去掉(保留 count 的 volatile),运行观察 count **依然小于 1000**——直接验证"volatile 不能保证原子性,必须 synchronized"
+- 把 threadCount 改成 10000 再运行,观察线程多时竞争更激烈、结果偏差更明显
+- 改用 `AtomicInteger` 代替 volatile Integer + synchronized,观察 `incrementAndGet()` 是否无需加锁也能保证原子性(展望拓展知识)
+
+**思考题**:
+1. `count++` 表面是一行代码,底层被拆成几步?(提示:3 步——①读当前 count 值 ②count+1 ③赋值给 count,即"读-改-写")
+2. 为什么 volatile 修饰了 count 还是小于 1000?(提示:volatile 只保证可见性,不保证原子性——"读-改-写"3 步依旧可能被打断,多个线程读旧值计算后互相覆盖)
+3. `synchronized` 为什么能保证原子性?(提示:同步实例方法锁 this,同一时刻只有一个线程能执行 count++,其他线程排队等待)
+4. `join()` 在这里起什么作用?(提示:让 main 等待 1000 个线程全部结束后再打印 count,否则可能打印到还没执行完的中间值)
+
+---
+
+## 练习三:Lock 锁基本用法 —— ReentrantLock 计数器(lock + unlock + finally)
+
+**难度**:⭐⭐⭐⭐
+**知识点**:`Lock` 接口、`ReentrantLock` 可重入锁、`lock()` / `unlock()`、unlock 必须放 **finally** 铁律、公平锁 / 非公平锁、10000 线程 + `join()` 验证
+
+**场景描述**:课堂学了 **Lock 锁(ReentrantLock 可重入锁)**:JDK1.5 提供 Lock 接口及实现类 ReentrantLock。使用 Lock 有一条铁律——**synchronized 是自动释放锁的,而 Lock 必须手动释放锁,且 `unlock()` 必须放到 finally 块中**,否则代码抛异常或提前 return 时锁永远不会释放,其他线程会永远阻塞(死锁)。本例用 ReentrantLock 实现一个计数器,验证 10000 线程并发后 count 正确。
+
+### 案例需求
+
+创建 LockCounter 类:`ReentrantLock lock` + `increment()`(lock → try → count++ → finally unlock),用 10000 线程并发验证 count 必然正确。
+
+### 实现步骤
+
+1. 定义 `ReentrantLock lock = new ReentrantLock();`(默认非公平锁;`new ReentrantLock(true)` 是公平锁)
+2. `increment()` 方法:`lock.lock()` 加锁 → `try { count++; } finally { lock.unlock(); }`
+3. main:创建 LockCounter,10000 个线程并发调用 increment()
+4. 用线程数组 + `join()` 等待所有线程结束
+5. 打印最终 count,验证正确性
+
+### 代码框架
+
+```java
+import java.util.concurrent.locks.ReentrantLock;
+
+public class LockCounterTest {
+    private Integer count = 0;
+    //创建锁对象---默认是非公平锁
+    // TODO 1: ReentrantLock lock = new ReentrantLock();
+    // 公平锁写法:new ReentrantLock(true);  按照申请顺序获取锁,性能略低
+
+    //---------基本用法:lock()+unlock() ,前提是必须要finally
+    public void increment() {
+        // TODO 2: lock.lock();    加锁
+        // TODO 3: try { count++; } finally { lock.unlock(); }   释放锁必须放finally
+    }
+
+    public Integer getCount() {
+        return count;
+    }
+
+    public static void main(String[] args) throws InterruptedException {
+        LockCounterTest lc = new LockCounterTest();
+
+        // TODO 4: 创建线程数组 Thread[10000],每个线程执行 lc.increment(),start()
+        Thread[] threads = new Thread[10000];
+        for (int i = 0; i < threads.length; i++) {
+            threads[i] = new Thread(lc::increment);
+            threads[i].start();
+        }
+
+        // TODO 5: 等待所有线程结束 for (Thread t : threads) t.join();
+
+        System.out.println("最终 count=" + lc.getCount());
+    }
+}
+```
+
+**输出示例**:
+```
+最终 count=10000    ← 加了 Lock 锁,count 必然正确
+```
+
+**选做加分**:
+- 把 `finally { lock.unlock(); }` 去掉(直接 count++ 后 unlock),模拟"提前 return 时锁不释放"的死锁场景
+- 在 increment() 里加 `if (count == 500) return;` 再运行,观察不加 finally 时是否死锁——体会铁律
+- 分别用 `new ReentrantLock()`(非公平)和 `new ReentrantLock(true)`(公平)创建锁,观察竞争线程获取锁的顺序差异
+
+**思考题**:
+1. 为什么 `unlock()` 必须放到 finally 块中?(提示:如果代码抛异常或提前 return,锁永远不会释放,其他线程永久阻塞——死锁)
+2. 非公平锁和公平锁的区别是什么?(提示:非公平锁释放后所有线程竞争(性能高);公平锁按申请顺序获取锁(性能略低))
+3. Lock 相比 synchronized 需要手动释放锁,这是缺点吗?(提示:是使用成本更高的体现,但也因此支持 tryLock / lockInterruptibly 等精细控制)
+4. `lc::increment` 是什么写法?(提示:方法引用,等价于 `() -> lc.increment()`)
+
+---
+
+## 练习四:Lock 锁进阶 —— tryLock / tryLock(timeout) / lockInterruptibly 避免死锁
+
+**难度**:⭐⭐⭐⭐⭐
+**知识点**:`tryLock()` 尝试获取锁、`tryLock(timeout, TimeUnit)` 限时等待、`lockInterruptibly()` 可中断获取锁、避免死锁、`InterruptedException`、catch 后重设中断标志
+
+**场景描述**:课堂学了 Lock 锁的**进阶用法**:基本用法 `lock()` 如果拿不到锁会**无限期等待**(可能死锁);而 `tryLock()` **获取不到就算了**(避免死锁的手段)、`tryLock(timeout)` **最多等指定时间**、`lockInterruptibly()` **等待中可被 interrupt() 中断**。本例实现三种进阶方法,体会 Lock 比 synchronized 更精细的锁控制。
+
+### 案例需求
+
+实现一个 LockDemo 类,包含三种进阶加锁方法(tryLock / tryLock(timeout) / lockInterruptibly),并演示如何避免死锁、响应中断。
+
+### 实现步骤
+
+1. `tryIncrement()`:`if (lock.tryLock())` 获取到就 count++ 并返回 true,没获取到返回 false
+2. `timedIncrement()`:`lock.tryLock(1, TimeUnit.SECONDS)` 最多等 1 秒,超时返回 false;处理 InterruptedException
+3. `interruptLock()`:`lock.lockInterruptibly()` 等待中可被 interrupt 打断;catch 中打印提示并重设中断标志
+4. main:创建 LockDemo,10000 线程并发调用 interruptLock(),join 等待后打印 count
+
+### 代码框架
+
+```java
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class LockAdvanceTest {
+    private Integer count = 0;
+    private final ReentrantLock lock = new ReentrantLock();
+
+    //--------进阶用法:tryLock()尝试获取锁,如果实在拿不到就算了,继续干别的。避免死锁的手段。
+    public boolean tryIncrement() {
+        // TODO 1: if (lock.tryLock()) {   //立刻尝试获取锁
+        //             try { count++; return true; }
+        //             finally { lock.unlock(); }
+        //         }
+        // TODO 2: return false;   //没抢到锁就返回false
+        return false;
+    }
+
+    //--------进阶用法:tryLock(timeout)限时等待
+    public boolean timedIncrement() {
+        // TODO 3: try {
+        //             if (lock.tryLock(1, TimeUnit.SECONDS)) { //最多等待1秒
+        //                 try { count++; return true; }
+        //                 finally { lock.unlock(); }
+        //             }
+        //         } catch (InterruptedException e) { throw new RuntimeException(e); }
+        // TODO 4: return false;
+        return false;
+    }
+
+    //--------进阶用法:可中断的获取锁(和sleep中断机制配合)
+    public void interruptLock() {
+        // TODO 5: 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 {
+        LockAdvanceTest ld = new LockAdvanceTest();
+
+        // TODO 6: 创建线程数组 Thread[10000],每个线程执行 ld::interruptLock,start()
+        Thread[] threads = new Thread[10000];
+        for (int i = 0; i < threads.length; i++) {
+            threads[i] = new Thread(ld::interruptLock);
+            threads[i].start();
+        }
+
+        // TODO 7: 等待所有线程结束 for (Thread t : threads) t.join();
+
+        System.out.println("最终 count=" + ld.getCount());
+    }
+}
+```
+
+**输出示例**:
+```
+最终 count=10000    ← 用 lockInterruptibly + finally unlock 保证原子性
+(若某线程在等待锁时被中断,会打印:等待锁的过程中被中断)
+```
+
+**选做加分**:
+- 写两个线程:一个持有锁不放(sleep 5 秒),另一个分别用 `tryLock()` / `tryLock(1,SECONDS)` / `lockInterruptibly()` 尝试拿锁,观察三种方法在"拿不到锁"时的不同表现(立即返回 false / 等 1 秒返回 false / 等待中被 interrupt 中断)
+- 在 `tryIncrement()` 外打印返回值,观察多个线程竞争锁时,拿不到锁的线程返回 false、继续执行而不阻塞——体会"避免死锁"
+- 在 main 中给一个正在等待 lockInterruptibly 的线程调用 `interrupt()`,观察 catch 中打印"等待锁的过程中被中断"
+
+**思考题**:
+1. `lock()` 和 `tryLock()` 的区别是什么?(提示:lock() 拿不到锁就无限期等待;tryLock() 立刻尝试,拿不到立即返回 false——tryLock 是避免死锁的手段)
+2. `tryLock(timeout)` 的 timeout 参数是什么意思?(提示:最多等待指定时间,超时还没拿到锁就返回 false——拿不到锁不会再傻等)
+3. `lockInterruptibly()` 和 `tryLock()` 都能避免死锁吗?(提示:都能!lockInterruptibly 是"继续等但可被中断";tryLock 是"拿不到就算了"——都是 Lock 比 synchronized 更精细的控制)
+4. 为什么 catch 里要 `Thread.currentThread().interrupt()` 重设中断标志?(提示:InterruptedException 抛出时会清除中断标志,重设后让上层 isInterrupted() 能感知到中断——回顾 08-10 中断机制)
+
+---
+
+## 练习五:综合应用 —— 用 Lock 锁改造卖票问题(synchronized vs Lock 对比)
+
+**难度**:⭐⭐⭐⭐⭐
+**知识点**:`synchronized` vs `Lock` 七维对比、Lock 锁解决数据竞争、卖票问题(相同票 / 负数票)、三种线程安全方案(synchronized / Lock / 原子类)、多线程共享同一对象
+
+**场景描述**:课堂学了 **synchronized vs Lock 的七维对比**(语法简洁度 / 锁释放 / 是否可中断 / 是否可超时等待 / 是否支持锁读 / 底层实现 Monitor Lock vs AQS / JDK21 版本更新性能相近)。08-10 的课后作业用 **synchronized** 修复卖票问题(Demo_03);本例要求用 **Lock 锁(ReentrantLock)** 改造同一个卖票问题,对比两种锁的写法差异,体会"解决同一个线程安全问题,synchronized 和 Lock 各有写法"。
+
+### 案例需求
+
+把 homework0810 Demo_03 的卖票类(SellTicket)中的 `synchronized(this)` 改造成 ReentrantLock 加锁(lock + try + finally unlock),3 个窗口线程卖 100 张票,验证不再出现相同票 / 负数票。
+
+### 实现步骤
+
+1. SellTicket 实现 Runnable,`private int tickets = 100;`
+2. 创建 `ReentrantLock lock`(锁对象)
+3. `sell()` 卖票逻辑:if (tickets > 0) { sleep(10); 打印出售信息; tickets--; }
+4. run() 中:`lock.lock()` → try { sell(); } finally { lock.unlock(); }
+5. main:创建同一个 SellTicket 对象,3 个窗口线程(窗口1/2/3)启动卖票
+6. 用线程数组 + `join()` 等待所有窗口卖票结束
+
+### 代码框架
+
+```java
+import java.util.concurrent.locks.ReentrantLock;
+
+public class LockSellTicketTest {
+    static class SellTicket implements Runnable {
+        private int tickets = 100;   // 一共 100 张票
+        // TODO 1: 创建 ReentrantLock lock = new ReentrantLock();
+
+        public void sell() {
+            if (tickets > 0) {
+                try {
+                    Thread.sleep(10);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                System.out.println(Thread.currentThread().getName() + "正在出售第 " + tickets + " 张票");
+                tickets--;
+            }
+        }
+
+        @Override
+        public void run() {
+            while (tickets > 0) {
+                // TODO 2: lock.lock(); → try { sell(); } finally { lock.unlock(); }
+                // 对比 08-10 作业的 synchronized(this) { sell(); }
+            }
+        }
+    }
+
+    public static void main(String[] args) throws InterruptedException {
+        SellTicket st = new SellTicket();
+
+        // TODO 3: 创建三个线程对象,模拟3个售票窗口(共用同一个st)
+        //         new Thread(st, "窗口1") / "窗口2" / "窗口3",start() 启动
+
+        // TODO 4: 用线程数组 + join() 等待所有线程卖票结束
+    }
+}
+```
+
+**输出示例**:
+```
+窗口1正在出售第 100 张票
+窗口3正在出售第 99 张票
+窗口2正在出售第 98 张票
+...
+窗口3正在出售第 1 张票
+        ← 每张票只出现一次,没有相同票 / 负数票
+```
+
+**选做加分**:
+- 把 `lock.lock()` 换成 `lock.tryLock()` 或 `lock.tryLock(1, TimeUnit.SECONDS)` 再运行,观察卖票结果是否仍然正确(可能某窗口没抢到票直接跳过本轮)
+- 对比总结:synchronized 写法(`synchronized(this) { sell(); }`)和 Lock 写法(lock + try + finally unlock)的**七维差异**,写一篇对比小结
+- 给 SellTicket 的卖票逻辑不用锁,多运行几次复现**相同票 / 负数票** bug,再加 Lock 锁修复——完整走一遍"复现 bug → Lock 修复"流程
+
+**思考题**:
+1. 为什么 3 个窗口必须**共用同一个** SellTicket 对象?(提示:只有共享同一个对象,lock 锁对象才一致,synchronized/Lock 才有效;若各自 new 对象,锁失效)
+2. Lock 改造卖票和 synchronized 改造卖票,哪个更简单?(提示:synchronized 语法更简单(只需关键字);Lock 需要 try-finally 块手动释放——这正对应七维对比中的"语法简洁度")
+3. 卖票问题为什么要锁住"判断-打印-减票"整体?(提示:这是读-改-写复合操作,必须整体原子执行,否则多个线程同时通过 if 判断导致相同票 / 负数票)
+4. 结合七维对比,什么场景适合用 Lock?(提示:需要超时等待、响应中断、公平锁、读写分离等精细控制时;简单场景用 synchronized 更合适)
+
+---
+
+# 参考答案要点
+
+## 练习一:VolatileVisibilityTest(volatile 可见性)
+
+```java
+// 1、work() 方法(TODO 1)
+public void work() {
+    System.out.println("work 线程开始......");
+    while (running) {
+        //空循环,什么都不做....
+    }
+    System.out.println("线程退出......");
+}
+
+// 2、stop() 方法(TODO 2)
+public void stop() {
+    running = false;
+}
+
+// 3、main 方法(TODO 3~5)
+public static void main(String[] args) throws InterruptedException {
+    VolatileVisibilityTest vd = new VolatileVisibilityTest();
+    Thread t = new Thread(vd::work, "worker");   // 方法引用创建线程
+    t.start();
+    Thread.sleep(1000);
+    vd.stop();   // 不加 volatile → 程序永不退出;加了 volatile → 正常退出
+}
+```
+
+## 练习二:CounterAtomicityTest(volatile vs synchronized)
+
+```java
+// 1、给 increment() 加 synchronized(TODO 1)
+public synchronized void increment() {
+    count++;    // 读-改-写 3 步
+}
+
+// 2、1000 线程并发 + join 等待(TODO 2~3)
+Thread[] threads = new Thread[threadCount];
+for (int i = 0; i < threadCount; i++) {
+    threads[i] = new Thread(() -> counter.increment());
+    threads[i].start();
+}
+for (Thread t : threads) {
+    t.join();
+}
+System.out.println("最终 count=" + counter.getCount());   // 必然是 1000
+```
+
+## 练习三:LockCounterTest(ReentrantLock 基本用法)
+
+```java
+// 1、创建锁对象(TODO 1)
+ReentrantLock lock = new ReentrantLock();   // 默认非公平锁
+
+// 2、increment():lock + try + finally unlock(TODO 2~3)
+public void increment() {
+    lock.lock();          // 加锁
+    try {
+        count++;
+    } finally {
+        lock.unlock();    // 释放锁,必须要释放,否则其他线程永远拿不到锁
+    }
+}
+
+// 3、10000 线程 + join 等待(TODO 4~5)
+Thread[] threads = new Thread[10000];
+for (int i = 0; i < threads.length; i++) {
+    threads[i] = new Thread(lc::increment);
+    threads[i].start();
+}
+for (Thread t : threads) {
+    t.join();
+}
+System.out.println("最终 count=" + lc.getCount());   // 必然是 10000
+```
+
+## 练习四:LockAdvanceTest(tryLock / tryLock(timeout) / lockInterruptibly)
+
+```java
+// 1、tryLock() 尝试获取锁(TODO 1~2)
+public boolean tryIncrement() {
+    if (lock.tryLock()) {    // 立刻尝试,获取到了就返回true,否则返回false
+        try {
+            count++;
+            return true;
+        } finally {
+            lock.unlock();
+        }
+    }
+    return false;   // 没抢到锁就返回false,继续干别的——避免死锁
+}
+
+// 2、tryLock(timeout) 限时等待(TODO 3~4)
+public boolean timedIncrement() {
+    try {
+        if (lock.tryLock(1, TimeUnit.SECONDS)) {   // 最多等待1秒
+            try {
+                count++;
+                return true;
+            } finally {
+                lock.unlock();
+            }
+        }
+    } catch (InterruptedException e) {
+        throw new RuntimeException(e);
+    }
+    return false;
+}
+
+// 3、lockInterruptibly() 可中断获取锁(TODO 5)
+public void interruptLock() {
+    try {
+        lock.lockInterruptibly();   // 等待锁的过程中可以被 interrupt() 打断
+        try {
+            count++;
+        } finally {
+            lock.unlock();
+        }
+    } catch (InterruptedException e) {
+        System.out.println("等待锁的过程中被中断");
+        Thread.currentThread().interrupt();   // 重设中断标志
+    }
+}
+
+// 4、main:10000 线程执行 interruptLock + join(TODO 6~7)
+Thread[] threads = new Thread[10000];
+for (int i = 0; i < threads.length; i++) {
+    threads[i] = new Thread(ld::interruptLock);
+    threads[i].start();
+}
+for (Thread t : threads) {
+    t.join();
+}
+System.out.println("最终 count=" + ld.getCount());   // 必然是 10000
+```
+
+## 练习五:LockSellTicketTest(Lock 改造卖票问题)
+
+```java
+// 1、创建锁对象(TODO 1)
+private final ReentrantLock lock = new ReentrantLock();
+
+// 2、run():lock + try + finally unlock 锁住卖票过程(TODO 2)
+@Override
+public void run() {
+    while (tickets > 0) {
+        lock.lock();
+        try {
+            sell();
+        } finally {
+            lock.unlock();
+        }
+    }
+}
+
+// 3、3 个窗口线程共用同一个 st + join 等待(TODO 3~4)
+SellTicket st = new SellTicket();
+Thread t1 = new Thread(st, "窗口1");
+Thread t2 = new Thread(st, "窗口2");
+Thread t3 = new Thread(st, "窗口3");
+t1.start();
+t2.start();
+t3.start();
+
+// join 等待(用线程数组写法)
+Thread[] windows = {t1, t2, t3};
+for (Thread t : windows) {
+    t.join();
+}
+```
+
+---
+
+# 涵盖知识点总览
+
+| 知识点 | 说明 |
+|--------|------|
+| `volatile` 可见性 | 写 volatile 变量立即刷主存、读 volatile 变量强制从主存读,禁止使用 CPU 缓存 |
+| JVM 内存模型 | 每个线程有独立工作内存(CPU 缓存);读变量优先从工作内存读、刷新到主内存有延迟 |
+| 可见性问题"程序永不退出" | worker 线程 while(running) 空循环,读不到 main 线程的修改 → 永不退出;volatile 修复 |
+| volatile 三大语义 | ①可见性 ②禁止重排序(有序性)③不保证原子性 |
+| 方法引用创建线程 | `new Thread(vd::work, "worker")` 等价于 Lambda、等价于匿名 Runnable |
+| `volatile` 不保证原子性 | count++ 的"读-改-写"3 步依旧可能被打断,volatile 修饰 count 也小于理论值 |
+| `synchronized` 保证原子性 | 同步实例方法锁 this,同一时刻只有一个线程执行 count++ → count 必然是 1000 |
+| `count++` 非原子操作 | 底层 3 步:读 → 改 → 写;多线程并发时读到旧值覆盖新值 |
+| 线程数组 + `join()` | 1000/10000 线程并发后,for 循环 t.join() 等待所有线程结束再打印结果 |
+| `Lock` 接口 / `ReentrantLock` | JDK1.5 提供的显式锁机制;可重入锁,比 synchronized 更精细 |
+| `lock()` / `unlock()` | Lock 基本用法;unlock **必须放 finally**,否则死锁 |
+| unlock 放 finally 铁律 | 代码抛异常或提前 return 时锁不释放 → 其他线程永久阻塞(死锁) |
+| `tryLock()` | 立刻尝试获取锁,拿到返回 true,没拿到返回 false——**避免死锁的手段** |
+| `tryLock(timeout, unit)` | 最多等待指定时间,超时返回 false——拿不到锁不会再傻等 |
+| `lockInterruptibly()` | 等待锁的过程中可被 interrupt() 打断;catch 后重设中断标志 |
+| 公平锁 / 非公平锁 | `new ReentrantLock()` 非公平(释放后竞争);`new ReentrantLock(true)` 公平(按申请顺序,性能略低) |
+| synchronized vs Lock 七维对比 | 语法简洁度 / 锁释放 / 可中断 / 超时等待 / 锁读(读写分离)/ 底层实现(Monitor Lock vs AQS)/ JDK21 版本更新(性能相近) |
+| Lock 解决数据竞争 | 卖票问题用 lock + try + finally unlock 锁住"判断-打印-减票"整体,消除相同票 / 负数票 |
+| 三种线程安全方案 | synchronized(关键字简单可靠)/ Lock(精细控制)/ 原子类(AtomicInteger 无锁 CAS 性能最优) |
+
+
+

Некоторые файлы не были показаны из-за большого количества измененных файлов