Преглед изворни кода

feat: 新增20260810课程内容——线程操作与生命周期(MyThread01~02线程调度与优先级/ThreadMethodDemo常用方法/线程6种状态),线程安全问题(synchronized同步锁:SellTicket卖票/Counter计数器/SynchronizedDemo三种写法),0808课后作业参考解答(homework0808 Callable+FutureTask带返回值求和),含课后作业/课堂笔记/课堂练习

WanJL пре 1 недеља
родитељ
комит
29e7bd2079

+ 51 - 0
授课代码/c260810/src/course/Counter.java

@@ -0,0 +1,51 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Counter
+ * @description
+ * @create 2026/8/10
+ */
+public class Counter {
+    private Integer count=0;
+
+    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 {
+        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());
+        /*
+
+
+
+
+
+
+
+         */
+    }
+}

+ 64 - 0
授课代码/c260810/src/course/Demo01.java

@@ -0,0 +1,64 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo01
+ * @description 线程操作方法
+ * @create 2026/8/10
+ */
+public class Demo01 {
+    /*
+        操作线程的方法:
+
+        public static void sleep(long millis) 线程休眠 ,让当前线程休眠 millis 毫秒
+
+
+        线程调度方式:
+            分时调度模型:所有的线程轮流获得CPU的使用权,平均分配每个线程占用CPU的时间片
+            抢占式调度模型:优先让优先级高的线程使用CPU,如果优先级相同,则会随机选择一个,
+            优先级高的线程获取的CPU时间片就更多一些。
+
+            Java使用的就是抢占式调度模型。
+           所谓的随机性,其实就是假如计算机只有一个CPU,那么CPU在某一个时刻只能执行一个指令。
+           线程只有得到了CPU时间片(使用权),才可以执行指令。所以多线程程序的执行是有随机性的,
+           谁抢到CPU时间片不确定。
+        设置线程的优先级:
+        public final int getPriority()  返回此线程的优先级
+        public final void setPriority(int newPriority)  修改此现场的优先级,线程默认优先级是5,范围是1~10
+
+
+
+     */
+    public static void main(String[] args) {
+        Runnable run =()->{
+            for (int i = 0; i < 100; i++) {
+                try {
+                    //休眠100毫秒
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                System.out.println(Thread.currentThread().getName()+"----->"+i);
+            }
+        };
+
+        Thread t1=new Thread(run);
+
+        Thread t2=new Thread(run);
+        System.out.println(t1.getName()+"线程优先级:"+t1.getPriority());
+        System.out.println(t2.getName()+"线程优先级:"+t2.getPriority());
+
+        t1.setPriority(8);
+        System.out.println(t1.getName()+"线程优先级:"+t1.getPriority());
+        System.out.println(t2.getName()+"线程优先级:"+t2.getPriority());
+
+
+
+        t1.start();
+        t2.start();
+
+
+    }
+
+}

+ 59 - 0
授课代码/c260810/src/course/Demo02.java

@@ -0,0 +1,59 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo02
+ * @description 守护线程
+ * @create 2026/8/10
+ */
+public class Demo02 {
+    /*
+        守护线程 是随着其他非守护线程的结束而结束
+        public final void setDaemon(boolean on) 将当前线程标记为守护线程,当运行的线程都是守护线程的时候,JVM虚拟机会退出
+
+        JVM只有当所有的普通线程(用户线程)都结束的时候,才会突出。
+        守护线程并不会阻碍JVM的退出。
+        普通线程就是我们正常创建的线程,main主线程也是用户线程。只要有任何一个线程存活,JVM进程就不会退出。
+        守护线程是属于服务型线程,专门为用户线程提供后台支持,当进程中只剩下守护线程的时候,JVM会直接结束,
+        守护线程会被强制终止,甚至都来不及执行finally块的代码。
+
+        守护线程最经典的使用场景就是“垃圾回收线程 GC Thread”,当所有的用户线程都执行完,
+        GC线程也就没有存在的意义了。JVM直接退出。
+        其他的场景比如:心跳检测、后台日志、监控统计等等
+
+
+        守护线程的特点:
+            设置时机:守护线程必须要在执行start()方法前设置,否则会抛出异常:java.lang.IllegalThreadStateException
+            继承性:守护线程创建的子线程也默认是守护线程
+            finally不保证:JVM退出的时候,守护线程会被强杀,finally不一定执行,不能用于资源清理
+            使用场景:心跳检测、后台日志、监控统计等等
+            判断是否是守护线程:public final boolean isDaemon()
+
+       Q: JVM退出的条件是“所有的用户线程全部结束”,如果main结束了,但是还有非守护线程的子线程在跑,JVM会退出吗?
+       A: 不会!只要还有一个用户线程存活,JVM就不会退出。也会正是因为这个原因,所以会有很多服务程序,卡着不退出的情况。
+     */
+
+
+
+    public static void main(String[] args) {
+        MyThread01 mt1=new MyThread01();
+        MyThread01 mt2=new MyThread01();
+        mt1.setName("线程1");
+        mt2.setName("守护线程");
+        //设置线程为守护线程
+        //普通线程执行完后,守护线程也就没有继续执行下去的必要了
+        mt2.setDaemon(true);
+
+        mt1.start();
+        mt2.start();
+
+
+
+
+    }
+
+
+
+
+}

+ 98 - 0
授课代码/c260810/src/course/Demo03.java

@@ -0,0 +1,98 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo03
+ * @description 线程生命周期
+ * @create 2026/8/10
+ */
+public class Demo03 {
+    /*
+        线程生命周期:也就是线程从生到死的过程,它可以分为这样几个阶段:
+            新建、就绪、运行、死亡。
+        但是在运行过程中,可能会因为其他方法调用,让线程出现一个新的阶段:阻塞
+
+        实际上Java中线程的生命周期状态,有6种:
+            新建--NEW
+                |- 已经创建Thread对象,但是还没有调用start()方法,这个时候还没有和操作系统底层线程进行关联。
+                |- new Thread();
+            可运行(就绪)--RUNNABLE
+                |- 已经调用了start()方法,可能正在运行,也可能在等待CPU时间片,已经开始和底层操作系统线程关联了。完全有操作系统进行调度。
+                |- start();
+            阻塞--BLOCKED
+                |- 等待获取监视器锁进入同步块,抢锁失败会被动阻塞,不占用CPU时间。
+                |- 竞争同步锁失败
+            等待--WAITING
+                |- 无限期的等待另一个线程的通知,不占用CPU时间
+                |- wait()
+                |- join();
+                |- LockSupport.park()
+            计时等待--TIMED_WAITING
+                |- 有明确等待时限,超时自动回复,不占用CPU时间。
+                |- sleep(ms)
+                |- wait(timeout)
+                |- join(timeout)
+            终止--TERMINATED
+                |- run()方法执行完毕,或抛出未捕获异常而结束,线程终止后会和底层线程取消关联。
+                |- run()方法结束
+        基于Thread.state 这个枚举类
+
+
+         RUNNABLE(可运行状态)是总枢纽,大多数状态都要回到RUNNABLE状态
+         抢锁失败-->BLOCKED,拿到锁-->RUNNABLE
+         主动休息-->TIMED_WAITING
+         无线等待-->WAITING
+         所有的阻塞状态都有办法回到RUNNABLE状态,只有TERMINATED状态是终点站。
+
+
+
+        操作系统层面一般把线程划分为5种状态:
+            新建、就绪、运行、阻塞、终结
+
+
+        线程操作方法:
+        public final void join()    让当前线程等待调用join()方法的那个线程执行完毕后再执行。
+            比如: t.join(); 表示当前线程等待t结束。
+        public final void join(long millis) 最多等待指定毫秒,超过了就不等了
+        public void interrupt() 终端目标线程,只是设置一个中断标志,不会杀死线程,并且当目标线程处在join/wait/sleep的时候会抛异常,并且清除中断标志。
+        public static void yield()  让当前线程主动让出CPU时间片,重新参与竞争,只是建议,调度器不一定采纳。
+        public final boolean isAlive() 判断线程是否存活,已经执行了start()但是未结束
+        public static native Thread currentThread() 获取当前正在运行的线程对象
+
+        Object类的关于线程操作的方法:
+        让线程等待
+        public final void wait()
+        public final void wait(long timeoutMillis)
+        public final void wait(long timeoutMillis, int nanos)
+        唤醒线程
+        public final native void notify()   唤醒线程
+        public final native void notifyAll()    唤醒所有线程
+     */
+    public static void main(String[] args) throws InterruptedException {
+        MyThread01 mt1=new MyThread01();
+        MyThread01 mt2=new MyThread01();
+        MyThread01 mt3=new MyThread01();
+        MyThread01 mt4=new MyThread01();
+        mt1.setName("线程1");
+        mt2.setName("线程2");
+        mt3.setName("线程3");
+        mt4.setName("线程4");
+
+
+        mt1.start();
+        mt2.start();
+        mt3.start();
+        mt4.start();
+
+        for (int i = 0; i <1000 ; i++) {
+            System.out.println(Thread.currentThread().getName()+"---->"+i);
+        }
+        //我们是在哪调用的join?是在main线程,也就是说
+        //当前的线程main线程,要等待mt2线程结束
+        mt2.join();
+
+
+    }
+
+}

+ 125 - 0
授课代码/c260810/src/course/Demo04.java

@@ -0,0 +1,125 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Demo04
+ * @description 线程安全问题
+ * @create 2026/8/10
+ */
+public class Demo04 {
+    /*
+        我们知道进程和进程之间的数据和内存空间是不共享的,是相互独立的。
+        一个进程中的多个线程之间的数据和内存是共享的。
+        那么多线程所带来的最大的隐患就是:数据竞争。
+        当多个线程同时读写同一个共享变量的时候,程序的执行结果将会变得不可预测。有时正确,有时错误。而且错误无法复现。
+        我们理解线程安全的第一步,就是亲手制作一个bug:
+        卖票问题:
+        模拟3个售票窗口(3个线程)同时卖100张票
+        运行后看效果:
+            1、相同的票出现了多次
+            2、出现了负数的票
+         原因:线程执行的随机性(抢占式调度)导致的,可能在卖票的过程中丢失了CPU的执行权,导致出现问题
+         相同的票出现了多次:
+            多个线程同时执行到if (tickets>0)判断通过,这个时候tickets还是同一个值,所以都是打印出 窗口3正在出售第10张票
+         出现了负数的票:
+            tickets已经减到1的时候,多个线程同时通过if判断if (tickets>0),然后一个线程执行tickets--变成0,另一个线程继续执行tickets--变成-1
+        程执行的随机性(抢占式调度)导致某个线程在“判断-输出-减票数”这三步的执行过程中,可能丢失CPU执行权,
+        由另一个线程趁机插入到对共享数据的操作,破坏了数据的完整性。
+
+        计数器问题:
+            看:course.Counter类
+            理论上应该是1000,但是实际上少于1000。
+            为什么结果不对?
+            因为count++ 并不是原子操作(原子操作就是无论是多少步,都是一次性全部成功,要么全部失败回滚)
+            count++,表面上是一行代码,但在JVM中被拆成了3步:
+                1、从内存读取count到寄存器(读)
+                2、在寄存器中加1(改)
+                3、把结果写会到内存(写)
+            当多个线程同时执行 读-改-写 三步 的时候,就可能发生 读到了旧值,用旧值进行计算,覆盖别人写入的结果。
+            导致丢失最新的结果
+
+        多线程安全问题引发原因(缺一不可):
+            1、多线程环境,至少两条路径在并发执行。
+            2、存在共享数据,多个线程访问同一个变量/对象。
+            3、有多条语句操作共享数据,并且这些语句之间存在 读-改-写 的符合操作,比如count++,比如ticket--等
+
+        解决多线程安全性问题的基本方案思想:
+            让程序不在具备产生安全问题的环境:
+                1、把多条操作共享数据的语句锁起来。
+                2、让任意时刻只有一个线程能执行这段代码,其他线程只能排队等待。
+
+
+        线程安全的三大特性,必须背下来 多线程的核心
+
+            原子性:操作不可分割,要么全部执行,要么全部不执行。防止 读-改-写 被并发打断。
+                |-解决方案:synchronized(线程同步)、Lock(锁)、Atomic类(原子类)
+            可见性:一个线程的修改对另一个线程立即可见。防止线程把变量换成在CPU缓存中,其他线程看不到修改。
+                |-解决方案:volatile、synchronized(线程同步)、Lock(锁)
+            有序性:代码按照书写顺序执行,不会被指令重排。防止编译器和CPU在执行的可能会对指令进行重排。
+                |-解决方案:volatile、synchronized(线程同步)
+        原可序(原子性、可见性、有序性),但凡是线程安全方案,最终都是围绕这三个特性的。
+
+        保证原子性:
+            最简单的最可靠的同步手段保证原子性:synchronized(线程同步)
+            synchronized(线程同步)是Java内置的关键字,也是使用最广泛的同步手段。
+            每个Java对象在底层都对应一把监视器锁(Monitor Lock),当线程进入到synchronized修饰的代码块之前,必
+            须要先获取这个锁。如果这个锁已经被其他线程拿到了,那么当前现在就会进入到BLOCKED(阻塞)状态排队等待。
+            持有锁的线程会执行完代码然后自动释放锁,在等待队列中的线程会重新开始进行新一轮的竞争。
+            synchronized优点:
+                解决了多线程的数据安全问题,保证了同一时刻只有一个线程能执行被保护的代码。
+            synchronized弊端:
+                当线程很多的时候,每个线程都有去竞争同一把锁,比较耗资源,无形当中就会降低程序的运行效率。
+                所以锁的粒度应该尽量小,只锁真正操作共享数据的代码,而不是整个方法。
+
+            synchronized修饰方法,该方法会变为同步方法。
+            synchronized修饰代码块,会变成同步代码块。
+
+
+            synchronized锁的是什么?
+
+                public synchronized void method01(){
+
+                    //假如300行代码,只有3行是修改共享数据的。那么先锁的是整个方法的300代码,粒度就太大了。
+                }
+            synchronized修饰的方法也就是同步方法。它锁的其实是当前的对象(this),锁住同一个对象的方法调用
+
+                public  static synchronized void method01(){
+
+                    //假如300行代码,只有3行是修改共享数据的。那么先锁的是整个方法的300代码,粒度就太大了。
+                }
+            synchronized修饰的静态方法也就是同步静态方法,它锁的其实是当前对象所属的类的Class对象。锁住的所有这个类的静态调用,全局唯一。
+
+                Object obj=new Object();
+                synchronized(obj){
+                    //这个synchronized代码块只锁 那几行修改共享数据的代码,这样粒度就小很多。
+                }
+            synchronized修饰的代码块,锁的是指定的任意对象obj,更加灵活,只锁需要保护的部分。
+
+     */
+    public static void main(String[] args) {
+        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();
+    }
+
+    public synchronized void method01(){
+
+        //假如300行代码,只有3行是修改共享数据的。那么先锁的是整个方法的300代码,粒度就太大了。
+    }
+
+    public void method02(){
+        Object obj=new Object();
+
+        synchronized(obj){
+            //这个synchronized代码块只锁 那几行修改共享数据的代码,这样粒度就小很多。
+        }
+    }
+}

+ 17 - 0
授课代码/c260810/src/course/MyThread01.java

@@ -0,0 +1,17 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title MyThread01
+ * @description
+ * @create 2026/8/10
+ */
+public class MyThread01 extends Thread{
+    @Override
+    public void run() {
+        for (int i = 0; i < 100; i++) {
+            System.out.println(this.getName()+"---->"+i);
+        }
+    }
+}

+ 17 - 0
授课代码/c260810/src/course/MyThread02.java

@@ -0,0 +1,17 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title MyThread02
+ * @description
+ * @create 2026/8/10
+ */
+public class MyThread02 extends Thread{
+    @Override
+    public void run() {
+        for (int i = 0; i < 100000; i++) {
+            System.out.println(this.getName()+"---->"+i);
+        }
+    }
+}

+ 30 - 0
授课代码/c260810/src/course/SellTicket.java

@@ -0,0 +1,30 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title SellTicket
+ * @description 卖票问题
+ * @create 2026/8/10
+ */
+public class SellTicket implements Runnable{
+    //一共100张票
+    private Integer tickets=100;
+    @Override
+    public void run() {
+        while (true){
+            //t1尽量后会把这段代码锁起来
+            if (tickets>0){
+                try {
+                    Thread.sleep(10);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                //窗口1正在出售票数
+                System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
+                //减一张票
+                tickets--;
+            }
+        }
+    }
+}

+ 67 - 0
授课代码/c260810/src/course/SynchronizedDemo.java

@@ -0,0 +1,67 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title SynchronizedDemo
+ * @description
+ * @create 2026/8/10
+ */
+public class SynchronizedDemo {
+    private Integer count=0;
+
+    //写法1---同步实例方法,锁的是当前的对象 this
+    public synchronized  void increment(){
+        count++;
+    }
+
+    //写法2---同步静态方法(锁的是当前类的Class对象)
+    private static int staticCount=0;
+    public static synchronized void staticIncrement(){
+        staticCount++;
+    }
+
+    //写法3---同步代码块(锁的是指定的对象,最灵活)
+
+    private final Object lock=new Object();
+
+    public void increment03(){
+        synchronized (lock){    //只锁这一个代码块,粒度更细,性能更好
+            count++;
+        }
+    }
+
+
+    //模拟业务场景--转账(多个变量要保持一致)
+    private int balance=100;
+    public void transfer(int amount){
+        //必须要把 检查-修改 这个过程锁住(同步)
+        synchronized (this){
+            if (balance>=amount){
+                balance-=amount;
+                System.out.println("转账"+amount+"成功,余额:"+balance);
+            }else {
+                System.out.println("余额不足");
+            }
+        }
+    }
+
+    public static void main(String[] args) throws InterruptedException {
+        SynchronizedDemo sd=new SynchronizedDemo();
+
+        //1000个线程同时调用同步方法   调用的是同步方法,必然是1000
+        Thread[] threads=new Thread[1000];
+        for (int i = 0; i < 1000; i++) {
+            threads[i]=new Thread(sd::increment03);
+            threads[i].start();
+        }
+
+        for (Thread t:threads){
+            t.join();
+        }
+        System.out.println("加锁后最终 count="+sd.count);
+
+    }
+
+
+}

+ 100 - 0
授课代码/c260810/src/course/ThreadMethodDemo.java

@@ -0,0 +1,100 @@
+package course;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title ThreadMethodDemo
+ * @description
+ * @create 2026/8/10
+ */
+public class ThreadMethodDemo {
+
+    public static void main(String[] args) throws InterruptedException {
+/*
+        //1、sleep()让当前线程休眠指定毫秒--TIME_WAITING 计时等待状态
+        System.out.println(Thread.currentThread().getName()+"开始睡2秒.....");
+        Thread.sleep(2000);
+        System.out.println(Thread.currentThread().getName()+"睡醒了");
+
+        //2、join()让当前线程等待指定线程结束后再继续
+        Thread worker=new Thread(()->{
+            for (int i = 0; i < 5; i++) {
+                System.out.println(Thread.currentThread().getName()+"工作中--->"+i);
+                try {
+                    Thread.sleep(200);
+                } catch (InterruptedException e) {
+                    //如果触发异常, 就中断当前线程
+                    Thread.currentThread().interrupt();
+
+                }
+            }
+        },"worker-1");
+
+        worker.start();
+        worker.join();  //main线程会等待worker执行完成后继续执行
+        System.out.println("worker已结束....main线程继续");
+
+        Thread t=new Thread(()->{
+            try {
+                Thread.sleep(5000);
+                System.out.println("任务完成.....");
+            } catch (InterruptedException e) {
+                //如果触发异常, 就中断当前线程
+                Thread.currentThread().interrupt();
+                System.out.println("任务被中断....");
+            }
+        },"worker-2");
+
+        t.start();
+        t.join(1000);   //main线程会等待1秒,不等5秒
+        System.out.println("main 最多等待1秒,现继续执行.....");
+*/
+
+        //3、interrupt() 中断操作,设置中断标志
+        Thread t1=new Thread(()->{
+            while (true){
+                //主动检查是否中断--检查中断标志
+                if (Thread.currentThread().isInterrupted()){
+                    System.out.println("检查到中断标志...正常退出");
+                    break;
+                }
+                System.out.println("工作.......");
+
+                try {
+                    //如果线程正在被 sleep/wait的时候被中断会抛异常并清除标志
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                    System.out.println("sleep中被中断....");
+                    //重新设置中断标志,方便上层能感知
+                    Thread.currentThread().interrupt();
+                    break;
+                }
+            }
+        },"worker-3");
+
+        t1.start();
+        Thread.sleep(300);  //让t1先跑一会儿
+        t1.interrupt(); //向t1发送中断请求
+
+
+        //4、yield() 主动让出CPU时间片
+        Thread t2=new Thread(()->{
+            for (int i = 0; i < 50; i++) {
+                Thread.yield();
+                System.out.println(Thread.currentThread().getName()+"第"+i+"轮");
+            }
+        },"yield线程");
+
+        t2.start();
+
+        /*
+            interrupt() 并不会杀死线程,而是设置一个中断标志,请求线程自行响应。
+            如果线程处在sleep()/wait()/join()等阻塞状态,收到中断标志后,会抛出异常,并清除中断标志
+            捕获异常后,应该冲耐心调用interrupt()恢复中断标志,否则外层代码会感知不到中断
+            线程自用interrupt()/Thread.interrupt()检查标志后,再决定是否退出。
+
+         */
+
+
+    }
+}

+ 56 - 0
授课代码/c260810/src/exericse/CooperationTest.java

@@ -0,0 +1,56 @@
+package exericse;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title CooperationTest
+ * @description
+ * @create 2026/8/10
+ */
+public class CooperationTest {
+    public static void main(String[] args) throws InterruptedException {
+        // ========== 1、join + yield 演示 ==========
+        Thread worker = new Thread(() -> {
+            for (int i = 0; i < 5; i++) {
+                // TODO 1: Thread.yield();   主动让出 CPU 时间片
+                Thread.yield();
+                // TODO 2: 打印 "worker-1 工作中---> i"
+                System.out.println(Thread.currentThread().getName()+"工作中--->"+i);
+                try {
+                    Thread.sleep(200);
+                } catch (InterruptedException e) {
+                    // TODO 3: Thread.currentThread().interrupt();   重设中断标志
+                    Thread.currentThread().interrupt();
+                    break;
+                }
+            }
+        }, "worker-1");
+
+        worker.start();
+        // TODO 4: worker.join();   主线程等待 worker-1 结束
+        worker.join();
+        // TODO 5: 打印 "worker-1 已结束,主线程继续..."
+        System.out.println(worker.getName()+"已结束,主线程继续...");
+
+        // ========== 2、interrupt 中断演示 ==========
+        Thread sleeper = new Thread(() -> {
+            try {
+                Thread.sleep(5000);   // 长任务:睡 5 秒
+                System.out.println("任务完成");   // 被中断就不会执行到这
+            } catch (InterruptedException e) {
+                // TODO 6: Thread.currentThread().interrupt();   重设中断标志
+                Thread.currentThread().interrupt();
+                // TODO 7: 打印 "sleep 中被中断,isInterrupted()= 当前值"
+                System.out.println("sleep 中被中断,isInterrupted()="+Thread.interrupted());
+            }
+        }, "worker-2");
+
+        sleeper.start();
+        Thread.sleep(500);       // 让 sleeper 先进入 sleep 状态
+        // TODO 8: sleeper.interrupt();   向 worker-2 发送中断请求
+        sleeper.interrupt();
+        sleeper.join();          // 主线程等 worker-2 处理完中断
+        // TODO 9: 打印 "主线程结束"
+        System.out.println("主线程结束...");
+    }
+}

+ 26 - 0
授课代码/c260810/src/exericse/Exercise01.java

@@ -0,0 +1,26 @@
+package exericse;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Exercise01
+ * @description
+ * @create 2026/8/10
+ */
+public class Exercise01 {
+    public static void main(String[] args) {
+        //1、使用继承Thread方式创建线程并使用线程
+        //2、使用runnable方式创建并使用线程
+        //3、使用callable方式创建并使用线程
+
+        /*
+        调用一些方法
+            获取当前线程名称的方法
+            设置当前线程名称的方法
+            获取线程返回结果的方法
+
+
+         */
+    }
+
+}

+ 60 - 0
授课代码/c260810/src/homework0808/Homework_03.java

@@ -0,0 +1,60 @@
+package homework0808;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+
+/**
+ * @author WanJl
+ * @version 1.0
+ * @title Homework_03
+ * @description
+ * @create 2026/8/10
+ */
+public class Homework_03 {
+    // ========== ① 实现 Callable 接口,泛型指定返回类型 ==========
+    static class SumCallable implements Callable<Integer> {
+        private int n;   // 求 1~n 的和
+
+        public SumCallable(int n) {
+            this.n = n;
+        }
+
+        @Override
+        public Integer call() throws Exception {
+            // TODO 1: int sum = 0; 计算 1~n 的累加和
+            int sum = 0;
+            // TODO 2: 打印 Thread.currentThread().getName() + "求和结果:" + sum
+            for (int i = 1; i <=n; i++) {
+                sum+=i;
+            }
+            System.out.println(Thread.currentThread().getName() + "求和结果:" + sum);
+            // TODO 3: return sum;   // 返回计算结果(get() 会拿到这个值)
+            return sum;
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        // TODO 4: 创建 3 个 SumCallable 对象(求 1~100 / 1~1000 / 1~10000 的和)
+        SumCallable sc1=new SumCallable(100);
+        SumCallable sc2=new SumCallable(1000);
+        SumCallable sc3=new SumCallable(10000);
+        // TODO 5: 用 FutureTask<Integer> 包装每个 Callable 对象
+        FutureTask<Integer> ft1=new FutureTask<>(sc1);
+        FutureTask<Integer> ft2=new FutureTask<>(sc2);
+        FutureTask<Integer> ft3=new FutureTask<>(sc3);
+        // TODO 6: new Thread(futureTask, "线程名") 创建 3 个线程并 start()
+        Thread t1=new Thread(ft1,"线程1");
+        Thread t2=new Thread(ft2,"线程2");
+        Thread t3=new Thread(ft3,"线程3");
+        t1.start();
+        t2.start();
+        t3.start();
+        // TODO 7: 调用 ft.get() 获取每个线程的返回值,打印并在 main 中汇总总结果
+        System.out.println(ft1.get());
+        System.out.println(ft2.get());
+        System.out.println(ft3.get());
+
+        // TODO 8: (思考)对比三种实现方式:run() 无返回值 vs call() 有返回值,各自适合什么场景
+    }
+
+}

+ 302 - 0
课后作业/2026年8月10日-作业.md

@@ -0,0 +1,302 @@
+# 2026年8月10日 课后作业 — 线程操作方法、生命周期、守护线程与 synchronized 线程同步
+
+> **说明**:本次课程学习了 **多线程的进阶内容**——**线程操作方法**(`Thread.sleep()` 线程休眠、`getPriority()` / `setPriority()` 线程优先级、抢占式调度模型与 CPU 时间片随机性、`join()` / `interrupt()` / `yield()` / `isAlive()`、Object 类 `wait()` / `notify()` / `notifyAll()`)、**线程生命周期**(`Thread.State` 枚举 6 种状态:NEW / RUNNABLE / BLOCKED / WAITING / TIMED_WAITING / TERMINATED、`getState()` 观察状态)、**守护线程**(`setDaemon()` / `isDaemon()`、JVM 退出机制、守护线程特点)、**线程安全问题**(数据竞争、卖票问题、计数器问题、线程安全三大特性:原子性 / 可见性 / 有序性)以及 **synchronized 线程同步**(监视器锁 Monitor Lock、三种写法:同步实例方法锁 this / 同步静态方法锁 Class 对象 / 同步代码块锁指定对象、锁粒度优化、转账业务场景)。
+>
+> 本作业共 **3 道题**:**基础题(必做)**——线程优先级 + 生命周期观察赛跑;**进阶题(必做)**——守护线程后台心跳监控(含 join 等待与状态观察);**挑战题(选做)**——synchronized 线程同步修复卖票问题。覆盖今日核心知识点。
+
+---
+
+## 📌 作业总体要求
+
+### 一、命名要求
+
+| 项目 | 要求 |
+|------|------|
+| **类名** | `Demo_题号_你的姓名`(如 `Demo_01_张三`、`Demo_02_李四`、`Demo_03_王五`) |
+| **文件头注释** | 每个 Java 文件头部需包含 `@author`、`@title`、`@create` 文档注释 |
+
+### 二、提交方式与截止时间
+
+| 项目 | 要求 |
+|------|------|
+| **提交方式** | 将源码文件 + 运行结果截图提交至班级指定平台 |
+| **截止时间** | 下次上课前 |
+
+---
+
+## 一、基础题(必做)
+
+### 题目 1:线程优先级 + 生命周期观察赛跑 RaceLifecycleDemo(⭐⭐ 必做)
+
+**知识点**:`setPriority()` / `getPriority()`(默认 5、范围 1~10)、`Thread.MAX_PRIORITY` / `NORM_PRIORITY` / `MIN_PRIORITY`、`Thread.sleep()` 线程休眠、抢占式调度模型与多线程执行随机性、**线程生命周期**(`Thread.State` 枚举、`getState()` 观察 NEW / RUNNABLE / TIMED_WAITING / TERMINATED)、Lambda 创建 Runnable、`join()` 等待线程结束
+
+**场景描述**:课堂用 `Demo01` 演示了 Lambda 创建 Runnable + `setPriority(8)` 设置优先级 + `sleep(100)` 让双线程并发交错;用 `Demo03` 的 `getState()` 观察线程生命周期状态。请综合实现:创建 **3 个"运动员"线程**(优先级分别为 10 / 5 / 1)赛跑 10 圈,每圈 `sleep(100)` 模拟跑步;同时用 `getState()` 观察**某个线程**在不同阶段的状态变化,体会优先级对抢占调度的影响和生命周期状态流转。
+
+**题目要求**:
+
+```java
+/**
+ * @author 你的姓名
+ * @title 题目1:线程优先级 + 生命周期观察赛跑
+ * @create 2026/8/10
+ */
+public class Demo_01_姓名 {
+    // ========== ① Lambda 创建 Runnable 运动员任务 ==========
+    static Runnable runner = () -> {
+        // TODO 1: String name = Thread.currentThread().getName();  // 获取线程名
+        // TODO 2: int p = Thread.currentThread().getPriority();    // 获取优先级
+        // TODO 3: 循环 10 圈,每圈 sleep(100) 后打印 name + "(优先级p)跑完第 i 圈"
+        // TODO 4: 循环结束打印 name + " 到达终点!"
+    };
+
+    public static void main(String[] args) throws InterruptedException {
+        // ========== ② 创建 3 个运动员线程 ==========
+        // TODO 5: new Thread(runner, "运动员1") / "运动员2" / "运动员3"
+        // TODO 6: 打印 3 个线程的默认优先级(预期都是 5)
+
+        // ========== ③ 设置不同优先级 ==========
+        // TODO 7: t1.setPriority(Thread.MAX_PRIORITY);    // 10
+        //         t2.setPriority(Thread.NORM_PRIORITY);   // 5
+        //         t3.setPriority(Thread.MIN_PRIORITY);    // 1
+
+        // ========== ④ 启动线程前观察 NEW 状态 ==========
+        // TODO 8: System.out.println("启动前状态:" + t1.getState());   // NEW
+
+        // ========== ⑤ 启动 3 个线程 ==========
+        // TODO 9: t1.start(); t2.start(); t3.start();
+
+        // ========== ⑥ 观察 t1 运行中的状态 ==========
+        // TODO 10: 主线程 sleep(50) 后打印 t1.getState()   // 可能是 RUNNABLE 或 TIMED_WAITING
+        // TODO 11: 用 t1.join() 等待 t1 结束,再打印 t1.getState()   // TERMINATED
+    }
+}
+```
+
+**预期输出示例**(每次运行顺序可能不同):
+
+```
+运动员1 默认优先级:5
+运动员2 默认优先级:5
+运动员3 默认优先级:5
+启动前状态:NEW
+运动员1(优先级10)跑完第 1 圈
+运动员3(优先级1)跑完第 1 圈
+运动员2(优先级5)跑完第 1 圈
+...
+运行中状态:TIMED_WAITING
+...
+运动员1 到达终点!
+结束后状态:TERMINATED
+```
+
+**要求说明**:
+1. 必须用 Lambda 创建 Runnable 任务,3 个线程共用一个任务对象
+2. 必须设置三种不同优先级(MAX=10 / NORM=5 / MIN=1),并打印验证
+3. 必须用 `getState()` 至少观察 **NEW / TIMED_WAITING / TERMINATED** 三个状态(RUNNABLE 尽量观察)
+4. 主线程最后必须用 `t1.join()` 等待 t1 结束
+
+**💡 提示**:
+- `Thread.MAX_PRIORITY`=10、`NORM_PRIORITY`=5(默认)、`MIN_PRIORITY`=1,`setPriority()` 超范围抛 `IllegalArgumentException`
+- 优先级只是提高获得 CPU 的**概率**,不保证先执行完(抢占式调度 + 随机性)
+- `sleep(100)` 期间线程处于 **TIMED_WAITING**;`new Thread()` 未 start 是 **NEW**;run() 结束是 **TERMINATED**
+- `getState()` 返回 `Thread.State` 枚举:`NEW` / `RUNNABLE` / `BLOCKED` / `WAITING` / `TIMED_WAITING` / `TERMINATED`
+
+**🎯 挑战加分(选做)**:
+1. 把 `setPriority(11)` 传入试试,观察抛出的 `IllegalArgumentException`
+2. 把循环圈数改大(如 1000)并去掉 sleep,多运行几次,统计"到达终点"顺序,观察高优先级线程是否更可能先到
+3. 尝试让两个线程竞争一个 `synchronized` 锁,观察未抢到锁线程的 `getState()` 是否为 **BLOCKED**(提前预习线程同步)
+
+---
+
+## 二、进阶题(必做)
+
+### 题目 2:守护线程后台心跳监控 + join 等待 DaemonHeartbeatDemo(⭐⭐⭐ 必做)
+
+**知识点**:**守护线程**(`setDaemon(true)` / `isDaemon()`、JVM 退出机制:所有用户线程结束才退出、只剩守护线程时 JVM 强制终止守护线程)、守护线程特点(必须在 `start()` 前设置、被强杀时 finally 不保证执行)、**用户线程 vs 守护线程**对比、`join()` 等待线程结束、`sleep()` 线程休眠、线程生命周期状态观察
+
+**场景描述**:课堂用 `Demo02` 演示了守护线程——"主任务(用户线程)" + "心跳监控(守护线程)",main 结束后 JVM 退出、守护线程被强制终止。请实现:**主任务线程**执行 5 秒业务(每秒打印一次进度),**心跳监控线程**(守护线程)每 500ms 报告一次心跳;主线程用 `join()` 等待主任务结束,观察 `main` 结束后心跳线程是否被 JVM 强制终止,并打印 `isDaemon()` 验证两个线程的守护属性。
+
+**题目要求**:
+
+```java
+/**
+ * @author 你的姓名
+ * @title 题目2:守护线程后台心跳监控 + join 等待
+ * @create 2026/8/10
+ */
+public class Demo_02_姓名 {
+    public static void main(String[] args) throws InterruptedException {
+        // ========== ① 用户线程(主任务):执行 5 秒 ==========
+        Thread task = new Thread(() -> {
+            for (int i = 1; i <= 5; i++) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();   // 重设中断标志
+                    break;
+                }
+                // TODO 1: 打印 "主任务执行中...第 i 秒"
+            }
+            // TODO 2: 打印 "主任务执行完毕!"
+        }, "主任务");
+
+        // ========== ② 守护线程(心跳监控):无限循环 ==========
+        Thread heart = new Thread(() -> {
+            int count = 0;
+            while (true) {
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    break;
+                }
+                // TODO 3: 打印 "心跳检测中...第 (++count) 次"
+            }
+        }, "心跳监控");
+
+        // ========== ③ 设置守护线程(必须在 start() 之前!)==========
+        // TODO 4: heart.setDaemon(true);
+
+        // ========== ④ 验证 isDaemon ==========
+        // TODO 5: 打印 heart.isDaemon()(预期 true)和 task.isDaemon()(预期 false)
+
+        // ========== ⑤ 启动两个线程 ==========
+        // TODO 6: task.start(); heart.start();
+
+        // ========== ⑥ 主线程 join 等待主任务结束 ==========
+        // TODO 7: task.join();   主线程等待主任务执行完
+        // TODO 8: 打印 "main 结束,观察心跳线程是否还在打印"
+        //         main 结束后只剩守护线程 → JVM 退出 → 心跳线程被强制终止
+    }
+}
+```
+
+**预期输出示例**(每次运行顺序可能不同):
+
+```
+心跳监控是否是守护线程:true
+主任务是否是守护线程:false
+主任务执行中...第 1 秒
+心跳检测中...第 1 次
+心跳检测中...第 2 次
+心跳检测中...第 3 次
+主任务执行中...第 2 秒
+...
+主任务执行完毕!
+main 结束,观察心跳线程是否还在打印
+        ← main 结束,JVM 退出,心跳检测不再打印
+```
+
+**要求说明**:
+1. 心跳线程必须用 `setDaemon(true)` 标记为守护线程,且必须在 `start()` **之前**调用
+2. 必须打印 `isDaemon()` 验证两个线程的守护属性(主任务 false、心跳 true)
+3. 主线程必须用 `task.join()` 等待主任务执行结束
+4. 运行后观察 main 结束后**心跳线程是否被强制终止**(不再打印)
+
+**💡 提示**:
+- **JVM 退出条件**:只有当所有用户线程(普通线程)都结束时 JVM 才退出;main 主线程也是用户线程
+- **守护线程被强杀**:当只剩守护线程时 JVM 直接退出,守护线程被强制终止,`finally` 不保证执行(所以不要用守护线程做资源清理)
+- 把 `heart.setDaemon(true)` 移到 `heart.start()` 之后会抛 `IllegalThreadStateException`
+- 如果把心跳线程改成**用户线程**(不 setDaemon),main 结束后 JVM **不会退出**,心跳会一直打印(对比体会)
+
+**🎯 挑战加分(选做)**:
+1. 把 `setDaemon(true)` 移到 `start()` 之后运行,观察抛出的 `IllegalThreadStateException`
+2. 把心跳线程改成用户线程(不 setDaemon),运行后观察 main 结束后 JVM 是否退出(心跳一直打印停不下来)
+3. 在心跳线程 run() 的 try-finally 中打印"资源清理",观察守护线程被强杀时 finally 是否执行(体会"finally 不保证")
+
+---
+
+## 三、挑战题(选做)
+
+### 题目 3:synchronized 线程同步 —— 修复卖票问题 SellTicketSyncDemo(⭐⭐⭐⭐ 选做)
+
+**知识点**:**线程安全问题**(数据竞争、进程间不共享 vs 线程间共享)、**卖票问题**(相同票多次出现 / 负数票两个 Bug 及其原因:读-改-写被并发打断)、**线程安全三大特性**(原子性 / 可见性 / 有序性)、**synchronized 三种写法**(同步实例方法锁 this / 同步静态方法锁 Class 对象 / 同步代码块锁指定对象)、**监视器锁(Monitor Lock)**、锁粒度优化、`join()` 等待线程结束
+
+**场景描述**:课堂 `Demo04` + `SellTicket` 演示了**卖票问题**——3 个售票窗口线程同时卖 100 张票,出现"相同的票多次出现"和"负数票"两个 Bug;`SynchronizedDemo` 用 synchronized 解决。请完成:**先写无 synchronized 的卖票代码复现 Bug**,再用 **synchronized 三种写法**(同步方法 / 同步代码块锁 this / 同步代码块锁自定义对象)修复,最后验证总票数正确、无重复、无负数。
+
+**题目要求**:
+
+```java
+/**
+ * @author 你的姓名
+ * @title 题目3:synchronized 线程同步修复卖票问题
+ * @create 2026/8/10
+ */
+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--; }
+        }
+
+        @Override
+        public void run() {
+            while (tickets > 0) {
+                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() 启动
+        // TODO 4: 用线程数组 + join() 等待所有线程卖票结束
+        // TODO 5: 运行多次观察:无 synchronized 时出现相同票 / 负数票;加 synchronized 后消失
+    }
+}
+```
+
+**预期输出示例**(无 synchronized 时——Bug 复现):
+
+```
+窗口1正在出售第10张票
+窗口2正在出售第10张票        ← Bug1:相同的票多次出现
+窗口3正在出售第10张票
+...
+窗口1正在出售第1张票
+窗口2正在出售第0张票          ← Bug2:负数票
+窗口3正在出售第-1张票
+```
+
+**预期输出示例**(加 synchronized 修复后):
+
+```
+窗口1正在出售第100张票
+窗口2正在出售第99张票
+窗口3正在出售第98张票
+...
+窗口3正在出售第1张票
+全部窗口卖票结束!共卖出100张票,无重复、无负数
+```
+
+**要求说明**:
+1. **先不加 synchronized 运行复现 Bug**(相同票多次出现、负数票),再给 `sell()` 加 `synchronized` 修复
+2. 用**同步方法(锁 this)**修复后,再改用 **同步代码块锁自定义对象**(`private final Object lock = new Object(); synchronized(lock){...}`)实现第二种写法
+3. 必须用**线程数组 + `join()`** 等待所有窗口线程结束,最后统计验证总票数
+4. 运行后对比"无锁 vs 加锁"的输出,用注释写出你的观察结论
+
+**💡 提示**:
+- **Bug 原因**:`if (tickets > 0)`(判断)与 `tickets--`(修改)之间是**读-改-写复合操作**,抢占式调度下线程随时可能被切换走,另一个线程趁机插入操作共享数据
+- **synchronized 作用**:保证**同一时刻只有一个线程**执行被保护的代码(拿不到锁的线程进入 **BLOCKED** 排队等待),从而保证**原子性**
+- **三种写法**:①同步实例方法 `public synchronized void sell()` 锁 this;②同步静态方法 `public static synchronized` 锁 Class 对象;③同步代码块 `synchronized(lock){}` 锁指定对象(**最灵活、粒度最细**)
+- **锁对象必须一致**:所有窗口线程共用**同一个** SellTicket 对象(同一个锁)才有效;如果每个线程 new 一个 SellTicket,锁对象不同就失效了
+- **线程安全三大特性**:原子性(防读-改-写打断→synchronized/Lock/Atomic)、可见性(防 CPU 缓存→volatile/锁)、有序性(防指令重排→volatile/synchronized)
+
+**🎯 挑战加分(选做)**:
+1. 实现**同步代码块锁自定义对象**和**同步静态方法(锁 Class 对象)**两种额外写法,对比三种写法的锁对象
+2. 用 `synchronized(this)` 写一个**转账方法** `transfer(int amount)`:把"检查余额 ≥ amount"和"余额 -= amount"整体锁住,模拟多个线程同时转账,验证余额不为负
+3. 在 `sell()` 中加 `Thread.sleep(50)` 放大并发窗口,观察不加锁时 Bug 复现更明显;加锁后再运行验证修复效果
+4. 画一张"线程安全三大特性 + 各自解决方案"表格,并把结论写在代码注释里
+
+---
+
+## 四、提交要求
+
+- **类名**:`Demo_题号_姓名`(如 `Demo_01_张三`、`Demo_02_李四`、`Demo_03_王五`)
+- **文件头注释**:每个 Java 文件头部必须包含 `@author`(你的姓名)、`@title`(题目名称)、`@create 2026/8/10`
+- **提交内容**:源码文件 + 运行结果截图
+- **必做/选做**:基础题(题目 1)、进阶题(题目 2)为**必做**;挑战题(题目 3)为**选做**,鼓励学有余力的同学完成
+- **运行环境**:JDK 8 及以上(挑战题使用 `synchronized` 关键字,JDK 自带)

+ 1439 - 0
课堂笔记/01-JavaSE基础-笔记/20260810-笔记.md

@@ -0,0 +1,1439 @@
+# 20260810 课堂笔记 — 线程操作方法(Thread.sleep 线程休眠 + 线程调度方式(分时 / 抢占式调度模型)+ 多线程执行随机性(CPU 时间片)+ 线程优先级 getPriority / setPriority(默认 5、范围 1~10)+ Lambda 创建 Runnable 双线程并发演示)+ 守护线程(setDaemon 守护线程与 JVM 退出机制 + 守护线程特点(设置时机 / 继承性 / finally 不保证 / isDaemon)+ GC 垃圾回收线程经典场景)+ 线程生命周期(6 种状态:NEW / RUNNABLE / BLOCKED / WAITING / TIMED_WAITING / TERMINATED + Thread.State 枚举 + 状态转换枢纽 RUNNABLE + 操作系统 5 状态对比)+ 线程操作方法(join() / join(long) 等待线程结束 + interrupt() / isInterrupted() 中断标志机制 + sleep 中断抛异常并清除标志 + yield() 主动让出 CPU 时间片 + isAlive() 判断线程存活 + Object 类 wait() / notify() / notifyAll() 等待唤醒)+ 线程安全问题(进程间不共享 vs 线程间共享 + 数据竞争概念 + 卖票问题(相同票多次出现 / 负数票)+ 计数器问题(count++ 非原子性:读-改-写 3 步)+ 多线程安全问题 3 个原因(缺一不可)+ 线程安全三大特性(原子性 / 可见性 / 有序性)及解决方案)+ synchronized 线程同步(监视器锁 Monitor Lock + BLOCKED 排队等待 + 三种写法(同步实例方法锁 this / 同步静态方法锁 Class 对象 / 同步代码块锁指定对象)+ 锁粒度优化(只锁共享数据代码)+ 转账业务场景 + 加锁后 count 正确性验证)+ 随堂练习(三种创建线程方式回顾 + 线程常用方法清单 + CooperationTest join/interrupt/yield 综合)+ 课后作业回顾(实现 Callable 接口 + FutureTask 带返回值求和 + 三种实现方式适用场景思考)
+
+- **日期**:2026-08-10
+- **项目**:`c260810`
+- **包路径**:`course` / `exericse` / `homework0808`
+- **作者**:WanJL
+
+---
+
+## 目录
+
+1. [Thread.sleep() —— 线程休眠](#1-threadsleep--线程休眠)
+2. [线程调度方式:分时调度模型 vs 抢占式调度模型](#2-线程调度方式分时调度模型-vs-抢占式调度模型)
+3. [多线程执行的随机性:CPU 时间片机制](#3-多线程执行的随机性cpu-时间片机制)
+4. [线程优先级:getPriority() / setPriority()](#4-线程优先级getpriority--setpriority)
+5. [Demo01 综合演示:sleep + 优先级 + Lambda 双线程并发](#5-demo01-综合演示sleep--优先级--lambda-双线程并发)
+6. [守护线程:setDaemon() 与 JVM 退出机制(Demo02)](#6-守护线程setdaemon-与-jvm-退出机制demo02)
+7. [线程生命周期:6 种状态(Demo03)](#7-线程生命周期6-种状态demo03)
+8. [线程操作方法:join() / interrupt() / yield() / wait-notify(ThreadMethodDemo)](#8-线程操作方法join--interrupt--yield--wait-notifythreadmethoddemo)
+9. [线程安全问题:数据竞争与三大特性(Demo04)](#9-线程安全问题数据竞争与三大特性demo04)
+10. [synchronized 线程同步:三种写法与锁机制(SynchronizedDemo)](#10-synchronized-线程同步三种写法与锁机制synchronizeddemo)
+11. [随堂练习:三种创建线程方式回顾 + 线程常用方法(Exercise01)](#11-随堂练习三种创建线程方式回顾--线程常用方法exercise01)
+12. [课后作业回顾:Callable + FutureTask 带返回值求和(Homework_03)](#12-课后作业回顾callable--futuretask-带返回值求和homework_03)
+13. [知识点全景总结](#13-知识点全景总结)
+14. [随堂练习要点](#14-随堂练习要点)
+15. [拓展阅读](#15-拓展阅读)
+
+---
+
+## 1. Thread.sleep() —— 线程休眠
+
+### 概念 —— 让当前线程暂停执行
+
+`Thread.sleep(long millis)` 是操作线程的**静态方法**,作用是**线程休眠**:让**当前线程**休眠 `millis` 毫秒,休眠期间线程让出 CPU,时间到后恢复执行。
+
+```java
+// 来源:course/Demo01.java(注释部分)
+/*
+    操作线程的方法:
+
+    public static void sleep(long millis) 线程休眠 ,让当前线程休眠 millis 毫秒
+*/
+```
+
+> **要点**:
+> - **静态方法**:`Thread.sleep(毫秒)` 直接通过线程类名调用,作用是让**当前正在执行的线程**休眠
+> - **单位是毫秒**:`Thread.sleep(100)` 表示休眠 100 毫秒
+> - **必须处理中断异常**:`sleep()` 会抛受检异常 `InterruptedException`,调用处必须 try-catch 或 throws(Demo01 中用的是 try-catch + 抛 RuntimeException)
+
+### sleep 的经典使用场景
+
+```java
+// 来源:course/Demo01.java(main 方法片段)
+Runnable run = () -> {
+    for (int i = 0; i < 100; i++) {
+        try {
+            //休眠100毫秒
+            Thread.sleep(100);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        System.out.println(Thread.currentThread().getName() + "----->" + i);
+    }
+};
+```
+
+> **要点**:
+> - sleep 放在循环里,让线程每隔 100 毫秒打印一次——**放慢执行速度**,方便观察多线程并发交替的效果
+> - 每次 sleep 都会**主动让出 CPU 时间片**,为其他线程创造执行机会,所以两条线程的输出会交替出现
+> - 中断异常用 try-catch 捕获后抛 `RuntimeException`(运行时异常),保证 Lambda 表达式无受检异常签名约束
+
+---
+
+## 2. 线程调度方式:分时调度模型 vs 抢占式调度模型
+
+### 概念 —— 操作系统如何决定"下一个执行哪个线程"
+
+线程调度就是**给线程分配 CPU 使用权**的规则。Java 中共有两种调度模型:
+
+```java
+// 来源:course/Demo01.java(注释部分)
+/*
+    线程调度方式:
+        分时调度模型:所有的线程轮流获得CPU的使用权,平均分配每个线程占用CPU的时间片
+        抢占式调度模型:优先让优先级高的线程使用CPU,如果优先级相同,则会随机选择一个,
+        优先级高的线程获取的CPU时间片就更多一些。
+
+        Java使用的就是抢占式调度模型。
+*/
+```
+
+### 两种调度模型对比表
+
+| 调度模型 | 分配规则 | 优先级作用 | 典型系统 |
+|----------|----------|------------|----------|
+| **分时调度模型** | 所有线程**轮流**获得 CPU 使用权,**平均分配**时间片 | 基本不区分优先级 | 早期分时操作系统 |
+| **抢占式调度模型** | **优先让优先级高的线程**使用 CPU;优先级相同则**随机选择**一个 | 优先级高的线程获取的 CPU 时间片**更多** | **Java(默认采用)** |
+
+> **要点**:
+> - **分时调度**讲究"公平":大家轮流来、时间片均分,每个线程占用 CPU 的时间片平均分配
+> - **抢占式调度**讲究"择优":**优先级高的线程优先**获得 CPU,且拿到的**时间片更多**;如果两个线程优先级相同,就**随机选择一个**执行
+> - **Java 使用抢占式调度模型**——所以设置线程优先级(第 4 节)会影响线程获得 CPU 的机会
+
+---
+
+## 3. 多线程执行的随机性:CPU 时间片机制
+
+### 概念 —— 为什么多线程程序的输出顺序不确定
+
+```java
+// 来源:course/Demo01.java(注释部分)
+/*
+   所谓的随机性,其实就是假如计算机只有一个CPU,那么CPU在某一个时刻只能执行一个指令。
+   线程只有得到了CPU时间片(使用权),才可以执行指令。所以多线程程序的执行是有随机性的,
+   谁抢到CPU时间片不确定。
+*/
+```
+
+### 随机性产生的两个前提
+
+```
+① 单 CPU 时:某一时刻 CPU 只能执行一个指令
+② 线程只有抢到 CPU 时间片(使用权)才能执行指令
+→ 多个线程"谁先抢到时间片"不确定 → 执行顺序具有随机性
+```
+
+> **要点**:
+> - **单 CPU 同一时刻只能执行一条指令**——这是并发切换的硬件前提
+> - **线程必须拿到 CPU 时间片(使用权)才能执行指令**——拿不到就排队等待
+> - **谁抢到时间片不确定** → 多线程程序的**执行顺序具有随机性**,每次运行结果可能不同
+> - 这就是为什么"线程1 / 线程2 哪个先打印、打印多少"无法预判,只能观察到输出**交错混排**
+
+---
+
+## 4. 线程优先级:getPriority() / setPriority()
+
+### 概念 —— 通过优先级影响抢占调度的机会
+
+在抢占式调度模型下,**优先级高的线程**获取 CPU 时间片更多。Java 提供两个方法查看和修改线程优先级:
+
+```java
+// 来源:course/Demo01.java(注释部分)
+/*
+    设置线程的优先级:
+    public final int getPriority()  返回此线程的优先级
+    public final void setPriority(int newPriority)  修改此现场的优先级,线程默认优先级是5,范围是1~10
+*/
+```
+
+### 优先级规则速查
+
+| 项目 | 说明 |
+|------|------|
+| `int getPriority()` | 返回此线程的**优先级** |
+| `void setPriority(int newPriority)` | **修改**此线程的优先级 |
+| **默认优先级** | **5** |
+| **取值范围** | **1 ~ 10**(数值越大优先级越高) |
+
+> **要点**:
+> - **getPriority()**:实例方法,返回当前线程对象的优先级数值
+> - **setPriority(int)**:实例方法,修改线程的优先级,参数范围 **1~10**
+> - **默认优先级是 5**:new 出来的线程默认优先级为 5(如 Thread.NORM_PRIORITY)
+> - **优先级与调度模型联动**:优先级影响的是抢占式调度下**获得 CPU 的机率与时间片多少**,而不是"优先级高的线程一定先执行完"
+> - 超范围设置会抛 `IllegalArgumentException`(1~10 之外的取值非法)
+
+---
+
+## 5. Demo01 综合演示:sleep + 优先级 + Lambda 双线程并发
+
+### 演示目标
+
+把今天三个知识点串起来:用 **Lambda** 创建同一个 Runnable 任务,创建两个线程 t1 / t2,**打印默认优先级**,再用 `setPriority(8)` 修改 t1 的优先级后再次打印,最后 `start()` 启动两个线程观察并发执行:
+
+```java
+// 来源:course/Demo01.java(main 方法完整逻辑)
+public static void main(String[] args) {
+    // Lambda 创建 Runnable 任务:休眠100毫秒后打印当前线程名和循环值
+    Runnable run = () -> {
+        for (int i = 0; i < 100; i++) {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+            System.out.println(Thread.currentThread().getName() + "----->" + i);
+        }
+    };
+
+    Thread t1 = new Thread(run);
+    Thread t2 = new Thread(run);
+
+    // 打印默认优先级(两个线程都是5)
+    System.out.println(t1.getName() + "线程优先级:" + t1.getPriority());
+    System.out.println(t2.getName() + "线程优先级:" + t2.getPriority());
+
+    // 修改 t1 的优先级为8,再次打印对比
+    t1.setPriority(8);
+    System.out.println(t1.getName() + "线程优先级:" + t1.getPriority());
+    System.out.println(t2.getName() + "线程优先级:" + t2.getPriority());
+
+    t1.start();
+    t2.start();
+}
+```
+
+### 运行流程拆解
+
+```
+1、Runnable run = () -> {...}       用 Lambda 创建任务(回顾方式二:Runnable)
+2、Thread t1 = new Thread(run)      两个线程共用同一个任务对象
+3、t1.getPriority() / t2.getPriority()   初始优先级都是 5(默认值)
+4、t1.setPriority(8)                把 t1 优先级改成 8,t2 仍为 5
+5、t1.start() / t2.start()          启动两个线程,run() 交替并发执行
+```
+
+> **要点**:
+> - **同一个 Runnable 两个线程**:t1 / t2 共用 `run` 这个 Lambda 任务——再次体会"任务与线程解耦"(Runnable 是任务,Thread 是线程承载者)
+> - **默认优先级验证**:初始 `getPriority()` 都返回 **5**,印证"线程默认优先级是 5"
+> - **setPriority 生效验证**:`t1.setPriority(8)` 后,t1 优先级变 8、t2 仍是 5——优先级是**每个线程独立**的属性
+> - **sleep + 并发观察**:两个线程各自休眠 100 毫秒后打印,输出会交错出现;t1 优先级更高,理论上抢占 CPU 的机会更多(但不会严格保证执行顺序)
+> - **线程名**:未指定名字时默认是 `Thread-0` / `Thread-1`,用 `Thread.currentThread().getName()` 获取
+
+---
+
+## 6. 守护线程:setDaemon() 与 JVM 退出机制(Demo02)
+
+### 概念 —— 什么是守护线程
+
+**守护线程**是随着**其他非守护线程的结束而结束**的线程。它与普通线程(用户线程)的区别,关键在于 **JVM 退出的条件**:
+
+```java
+// 来源:course/Demo02.java(注释部分)
+/*
+    守护线程 是随着其他非守护线程的结束而结束
+    public final void setDaemon(boolean on) 将当前线程标记为守护线程,当运行的线程都是守护线程的时候,JVM虚拟机会退出
+
+    JVM只有当所有的普通线程(用户线程)都结束的时候,才会退出。
+    守护线程并不会阻碍JVM的退出。
+    普通线程就是我们正常创建的线程,main主线程也是用户线程。只要有任何一个线程存活,JVM进程就不会退出。
+    守护线程是属于服务型线程,专门为用户线程提供后台支持,当进程中只剩下守护线程的时候,JVM会直接结束,
+    守护线程会被强制终止,甚至都来不及执行finally块的代码。
+*/
+```
+
+### 核心方法 —— setDaemon()
+
+```java
+// 来源:course/Demo02.java(注释部分)
+public final void setDaemon(boolean on)   // 将当前线程标记为守护线程
+public final boolean isDaemon()           // 判断当前线程是否是守护线程
+```
+
+### 用户线程 vs 守护线程对照表
+
+| 对比维度 | 用户线程(普通线程) | 守护线程 |
+|----------|----------------------|----------|
+| **是否阻碍 JVM 退出** | **阻碍**——只要有任何一个用户线程存活,JVM 就不会退出 | **不阻碍**——当运行的线程都是守护线程时,JVM 退出 |
+| **角色定位** | 正常创建的线程(main 主线程也是用户线程) | **服务型线程**,专门为用户线程提供**后台支持** |
+| **被强制终止** | 正常执行完才结束 | 当只剩守护线程时 JVM 直接结束,守护线程被**强制终止**(连 finally 都来不及执行) |
+| **经典场景** | 业务逻辑(求和、打印、IO) | **GC 垃圾回收线程**、心跳检测、后台日志、监控统计 |
+
+> **要点**:
+> - **JVM 退出条件**:只有当**所有的用户线程(普通线程)都结束**时 JVM 才会退出;**只要还有一个用户线程存活,JVM 就不会退出**——这正是很多服务程序"卡着不退"的原因
+> - **守护线程不阻碍退出**:守护线程是服务型线程,专门为用户线程提供后台支持;当**只剩下守护线程**时,JVM 会**直接结束**,守护线程被**强制终止**
+> - **经典场景 GC 线程**:垃圾回收线程(GC Thread)就是最典型的守护线程——所有用户线程都执行完,GC 也就没有存在的意义,JVM 直接退出
+> - **其他场景**:心跳检测、后台日志、监控统计等"后台支撑型"任务都适合用守护线程
+
+### 守护线程的四大特点
+
+```java
+// 来源:course/Demo02.java(注释部分)
+/*
+    守护线程的特点:
+        设置时机:守护线程必须要在执行start()方法前设置,否则会抛出异常:java.lang.IllegalThreadStateException
+        继承性:守护线程创建的子线程也默认是守护线程
+        finally不保证:JVM退出的时候,守护线程会被强杀,finally不一定执行,不能用于资源清理
+        使用场景:心跳检测、后台日志、监控统计等等
+        判断是否是守护线程:public final boolean isDaemon()
+*/
+```
+
+| # | 特点 | 说明 |
+|---|------|------|
+| 1 | **设置时机** | `setDaemon(true)` 必须要在 `start()` **之前**调用,否则抛 `IllegalThreadStateException` |
+| 2 | **继承性** | 守护线程**创建的子线程也默认是守护线程**(继承守护属性) |
+| 3 | **finally 不保证** | JVM 退出时守护线程被**强杀**,`finally` **不一定执行**——**不能**用于资源清理 |
+| 4 | **判断方法** | `isDaemon()` 判断当前线程是否是守护线程 |
+
+> **要点**:
+> - **设置必须在 start() 之前**:`setDaemon(true)` 调用时机错误会抛 `IllegalThreadStateException`
+> - **守护属性会继承**:守护线程创建的子线程默认也是守护线程
+> - **强杀时 finally 不可靠**:守护线程在 JVM 退出时被强杀,`finally` 块不保证执行——所以**不要**用守护线程做资源清理(如关闭文件、释放连接)
+> - **适合后台支撑型任务**:心跳检测、后台日志、监控统计等"可有可无、随主线程而生灭"的任务
+
+### main 演示 —— 普通线程 + 守护线程对比
+
+```java
+// 来源:course/Demo02.java(main 方法,本次更新修正设置时机)
+public static void main(String[] args) {
+    MyThread01 mt1 = new MyThread01();
+    MyThread01 mt2 = new MyThread01();
+    mt1.setName("线程1");
+    mt2.setName("守护线程");
+    // 设置线程为守护线程
+    // 普通线程执行完后,守护线程也就没有继续执行下去的必要了
+    mt2.setDaemon(true);   // 必须在 start() 之前设置,否则抛 IllegalThreadStateException
+
+    mt1.start();
+    mt2.start();
+}
+```
+
+### 演示用线程类 —— MyThread01(本次已抽取为独立文件)
+
+```java
+// 来源:course/MyThread01.java(本次更新从 Demo02 内部类抽取为独立类)
+public class MyThread01 extends Thread {
+    @Override
+    public void run() {
+        for (int i = 0; i < 100; i++) {
+            try {
+                Thread.sleep(1000);   // 每隔 1 秒打印一次,共 100 次(约 100 秒)
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+            System.out.println(this.getName() + "---->" + i);
+        }
+    }
+}
+```
+
+### 思考题 —— main 结束后 JVM 会退出吗
+
+```java
+// 来源:course/Demo02.java(注释部分)
+/*
+   Q: JVM退出的条件是"所有的用户线程全部结束",如果main结束了,但是还有非守护线程的子线程在跑,JVM会退出吗?
+   A: 不会!只要还有一个用户线程存活,JVM就不会退出。也正是因为这个原因,所以会有很多服务程序,卡着不退出的情况。
+*/
+```
+
+> **要点**:
+> - **main 结束 ≠ JVM 退出**:只要还有一个**用户线程**存活,JVM 就不会退出——子线程会把 JVM"拖住"
+> - **服务程序卡住的原因**:很多后台服务程序"关不掉、卡着不退",往往就是还有用户线程在跑(如未停止的监听线程、任务线程)
+> - **设置时机修正(本次更新)**:代码更新前 `setDaemon(true)` 写在 `start()` **之后**,实际运行会抛 `IllegalThreadStateException`;本次已把 `setDaemon(true)` **移到 `start()` 之前**,保证守护线程设置合法生效——正对应"设置时机"特点
+> - **正确演示思路**:让"线程1"是普通用户线程(持续打印),让"守护线程"在用户线程结束后被 JVM 强制终止——直观体会"守护线程随用户线程结束而结束"
+
+---
+
+## 7. 线程生命周期:6 种状态(Demo03)
+
+### 概念 —— 线程从生到死的阶段
+
+**线程生命周期**就是**线程从生到死的过程**。直观上可分为 新建、就绪、运行、死亡 四个阶段,但**运行过程中**可能因为方法调用进入新的阶段——**阻塞**。实际上 Java 中线程的生命周期状态有 **6 种**,基于 **`Thread.State` 这个枚举类**:
+
+```java
+// 来源:course/Demo03.java(注释部分)
+/*
+    线程生命周期:也就是线程从生到死的过程,它可以分为这样几个阶段:
+        新建、就绪、运行、死亡。
+    但是在运行过程中,可能会因为其他方法调用,让线程出现一个新的阶段:阻塞
+
+    实际上Java中线程的生命周期状态,有6种:
+        新建--NEW
+        ...
+        终止--TERMINATED
+    基于Thread.state 这个枚举类
+*/
+```
+
+### 线程的 6 种状态速查表
+
+| 状态 | 枚举名 | 触发条件 | 说明 |
+|------|--------|----------|------|
+| **新建** | `NEW` | `new Thread()` | 已创建 Thread 对象,**还没调用 start()**,尚未与操作系统底层线程关联 |
+| **可运行(就绪)** | `RUNNABLE` | `start()` | 已调用 start(),**可能正在运行**,也可能在**等待 CPU 时间片**;已与底层线程关联,**完全由操作系统调度** |
+| **阻塞** | `BLOCKED` | 竞争同步锁失败 | **等待获取监视器锁进入同步块**,抢锁失败会被动阻塞,**不占用 CPU 时间** |
+| **等待** | `WAITING` | `wait()` / `join()` / `LockSupport.park()` | **无限期**等待另一个线程的通知,不占用 CPU 时间 |
+| **计时等待** | `TIMED_WAITING` | `sleep(ms)` / `wait(timeout)` / `join(timeout)` | **有明确等待时限**,超时自动恢复,不占用 CPU 时间 |
+| **终止** | `TERMINATED` | `run()` 方法结束 | run() 执行完毕或抛出**未捕获异常**而结束,线程终止后与底层线程**取消关联** |
+
+```java
+// 来源:course/Demo03.java(注释部分)
+/*
+    新建--NEW
+        |- 已经创建Thread对象,但是还没有调用start()方法,这个时候还没有和操作系统底层线程进行关联。
+        |- new Thread();
+    可运行(就绪)--RUNNABLE
+        |- 已经调用了start()方法,可能正在运行,也可能在等待CPU时间片,已经开始和底层操作系统线程关联了。完全有操作系统进行调度。
+        |- start();
+    阻塞--BLOCKED
+        |- 等待获取监视器锁进入同步块,抢锁失败会被动阻塞,不占用CPU时间。
+        |- 竞争同步锁失败
+    等待--WAITING
+        |- 无限期的等待另一个线程的通知,不占用CPU时间
+        |- wait()
+        |- join();
+        |- LockSupport.park()
+    计时等待--TIMED_WAITING
+        |- 有明确等待时限,超时自动回复,不占用CPU时间。
+        |- sleep(ms)
+        |- wait(timeout)
+        |- join(timeout)
+    终止--TERMINATED
+        |- run()方法执行完毕,或抛出未捕获异常而结束,线程终止后会和底层线程取消关联。
+        |- run()方法结束
+*/
+```
+
+> **要点**:
+> - **NEW**:对象建好了但**没 start()**,此时还没和**操作系统底层线程**关联(纯粹是 Java 层的 Thread 对象)
+> - **RUNNABLE**:start() 之后**开始和底层线程关联**,**完全由操作系统调度**——它既包括"正在运行",也包括"排队等 CPU 时间片"(Java 把运行和就绪合并成一个 RUNNABLE)
+> - **BLOCKED**:`synchronized` 抢**监视器锁**失败被**被动阻塞**(后续线程同步课程会用到),不占用 CPU
+> - **WAITING vs TIMED_WAITING**:都是"等",区别是 WAITING **无限期**等(wait()/join()/LockSupport.park()),TIMED_WAITING **限时等**(sleep(ms)/wait(timeout)/join(timeout)),超时自动恢复
+> - **TERMINATED**:run() 执行完毕**或抛出未捕获异常**而结束,终止后与底层线程**取消关联**
+> - **第 1 节复习**:`Thread.sleep(ms)` 让线程进入的就是 **TIMED_WAITING(计时等待)** 状态——超时自动恢复
+
+### 状态转换规律 —— RUNNABLE 是总枢纽,TERMINATED 是终点站
+
+```java
+// 来源:course/Demo03.java(注释部分)
+/*
+    RUNNABLE(可运行状态)是总枢纽,大多数状态都要回到RUNNABLE状态
+    抢锁失败-->BLOCKED,拿到锁-->RUNNABLE
+    主动休息-->TIMED_WAITING
+    无线等待-->WAITING
+    所有的阻塞状态都有办法回到RUNNABLE状态,只有TERMINATED状态是终点站。
+*/
+```
+
+### 状态转换图
+
+```
+                        ┌───────────────┐
+     new Thread()       │               │ run() 结束
+        │               ▼               ▼
+   ┌─────────┐   start()   ┌─────────┐   TERMINATED(终点站)
+   │  NEW    │ ──────────► │ RUNNABLE│
+   └─────────┘             └────┬────┘
+        ▲                       │
+        │              ┌────────┼────────┐
+        │              ▼        ▼        ▼
+        │         ┌────────┐ ┌────────┐ ┌────────┐
+        │         │BLOCKED │ │WAITING │ │TIMED_  │
+        │         │抢锁失败 │ │wait()  │ │WAITING │
+        │         │        │ │join()  │ │sleep() │
+        │         └────────┘ └────────┘ └────────┘
+        │              ▲        ▲        ▲
+        │              └────────┼────────┘
+        │                   回到RUNNABLE
+        │        (阻塞状态都有办法回到RUNNABLE,只有TERMINATED是终点)
+```
+
+> **要点**:
+> - **RUNNABLE 是总枢纽**:大多数状态都要**回到 RUNNABLE**——抢锁失败→BLOCKED,拿到锁→RUNNABLE;主动休息→TIMED_WAITING;无限等待→WAITING
+> - **所有阻塞状态都能回到 RUNNABLE**:BLOCKED / WAITING / TIMED_WAITING 都有办法恢复执行
+> - **TERMINATED 是终点站**:线程一旦终止就**回不去了**——只有 TERMINATED 状态不可逆
+
+### 操作系统层面 vs Java 层面的状态划分
+
+```java
+// 来源:course/Demo03.java(注释部分)
+/*
+    操作系统层面一般把线程划分为5种状态:
+        新建、就绪、运行、阻塞、终结
+*/
+```
+
+| 层面 | 状态数量 | 状态清单 |
+|------|----------|----------|
+| **操作系统层面** | 5 种 | 新建、就绪、**运行**、阻塞、终结 |
+| **Java(Thread.State)层面** | 6 种 | NEW、**RUNNABLE(就绪+运行合并)**、BLOCKED、WAITING、TIMED_WAITING、TERMINATED |
+
+> **要点**:
+> - **区别在于"运行/就绪"**:操作系统把"就绪"和"运行"分开(2 个状态);Java 把二者**合并成 RUNNABLE**(1 个状态)——因为 Java 无法精确区分"正在跑"还是"排队等 CPU"
+> - **Java 多出来的 WAITING / TIMED_WAITING**:这两个是 Java API 层面的"等待"细分,操作系统层统一归入"阻塞"
+
+---
+
+## 8. 线程操作方法:join() / interrupt() / yield() / wait-notify(ThreadMethodDemo)
+
+### 概念 —— join():让当前线程等待目标线程结束
+
+```java
+// 来源:course/Demo03.java(注释部分,本次更新新增 yield / isAlive / wait / notify)
+/*
+    线程操作方法:
+    public final void join()    让当前线程等待调用join()方法的那个线程执行完毕后再执行。
+        比如: t.join(); 表示当前线程等待t结束。
+    public final void join(long millis) 最多等待指定毫秒,超过了就不等了
+    public void interrupt() 终端目标线程,只是设置一个中断标志,不会杀死线程,并且当目标线程处在join/wait/sleep的时候会抛异常,并且清除中断标志。
+    public static void yield()  让当前线程主动让出CPU时间片,重新参与竞争,只是建议,调度器不一定采纳。
+    public final boolean isAlive() 判断线程是否存活,已经执行了start()但是未结束
+    public static native Thread currentThread() 获取当前正在运行的线程对象
+
+    Object类的关于线程操作的方法:
+    让线程等待
+    public final void wait()
+    public final void wait(long timeoutMillis)
+    public final void wait(long timeoutMillis, int nanos)
+    唤醒线程
+    public final native void notify()   唤醒线程
+    public final native void notifyAll()    唤醒所有线程
+*/
+```
+
+### 线程操作方法速查表(本次更新扩展)
+
+| 方法 | 归属 | 作用 |
+|------|------|------|
+| `final void join()` | Thread | **当前线程等待**调用 join() 的那个线程**执行完毕后再执行**(如 `t.join()` 表示当前线程等待 t 结束) |
+| `final void join(long millis)` | Thread | **最多等待指定毫秒**,超过就不等了 |
+| `void interrupt()` | Thread | **中断**目标线程:只是**设置一个中断标志**,不会杀死线程;目标线程处在 join/wait/sleep 时会**抛异常**并**清除中断标志** |
+| `static void yield()` | Thread | 让当前线程**主动让出 CPU 时间片**,重新参与竞争——只是**建议**,调度器**不一定采纳** |
+| `final boolean isAlive()` | Thread | **判断线程是否存活**:已经执行了 `start()` 但**未结束** |
+| `static native Thread currentThread()` | Thread | 获取**当前正在运行**的线程对象 |
+| `final void wait()` | **Object** | **让线程等待**:无限期等待(配合 notify 使用) |
+| `final void wait(long timeoutMillis)` | Object | 让线程**限时等待**指定毫秒 |
+| `final void wait(long timeoutMillis, int nanos)` | Object | 让线程限时等待(毫秒 + 纳秒精度) |
+| `final native void notify()` | Object | **唤醒**一个正在等待的线程 |
+| `final native void notifyAll()` | Object | **唤醒所有**正在等待的线程 |
+
+> **要点**:
+> - **方法归属分两类**:`join` / `interrupt` / `yield` / `isAlive` / `currentThread` 是 **Thread 类**的线程操作方法;`wait` / `notify` / `notifyAll` 是 **Object 类**的线程操作方法(注意 `wait()` 不是 Thread 的方法)
+> - **yield 是"建议"**:`yield()` 主动让出 CPU 时间片只是**建议**,调度器**不一定采纳**(与抢占式调度模型呼应)
+> - **isAlive 判活**:执行了 `start()` 但未结束的线程算"存活";`TERMINATED` 后返回 false
+> - **wait/notify 依赖锁**:`wait()` / `notify()` 是线程**通信/同步**的基础(等待与唤醒配对),后续线程同步课程会深入
+
+### Demo03 main 演示 —— main 线程等待 mt2 线程结束
+
+```java
+// 来源:course/Demo03.java(main 方法)
+public static void main(String[] args) throws InterruptedException {
+    MyThread01 mt1 = new MyThread01();
+    MyThread01 mt2 = new MyThread01();
+    MyThread01 mt3 = new MyThread01();
+    MyThread01 mt4 = new MyThread01();
+    mt1.setName("线程1");
+    mt2.setName("线程2");
+    mt3.setName("线程3");
+    mt4.setName("线程4");
+
+    mt1.start();
+    mt2.start();
+    mt3.start();
+    mt4.start();
+
+    for (int i = 0; i < 1000; i++) {
+        System.out.println(Thread.currentThread().getName() + "---->" + i);
+    }
+    //我们是在哪调用的join?是在main线程,也就是说
+    //当前的线程main线程,要等待mt2线程结束
+    mt2.join();
+}
+```
+
+> **要点**:
+> - **4 个子线程 + main 并发**:mt1~mt4 各打印 0~99,main 打印 0~999——五条执行路径并发
+> - **join 的调用方是 main**:`mt2.join()` 写在 main 方法里,**当前线程 main 等待 mt2 线程结束**后再继续
+> - **join 与 sleep 的关系**:调用 join() 的线程(main)进入 **WAITING(等待)** 状态;join(timeout) 则进入 **TIMED_WAITING**(对应第 7 节状态表)
+> - **main 抛异常**:`throws InterruptedException`(join 是受检方法)
+
+### ThreadMethodDemo —— sleep / join / interrupt / yield 综合演示
+
+`ThreadMethodDemo` 把 sleep、join、interrupt、yield 四种线程操作方法做了**综合演示**,其中 sleep 与 join 部分被**注释保留**,活动代码演示 **interrupt() 中断机制** 与 **yield() 让出 CPU**:
+
+```java
+// 来源:course/ThreadMethodDemo.java(注释部分,sleep / join 演示)
+//1、sleep()让当前线程休眠指定毫秒--TIME_WAITING 计时等待状态
+System.out.println(Thread.currentThread().getName() + "开始睡2秒.....");
+Thread.sleep(2000);
+System.out.println(Thread.currentThread().getName() + "睡醒了");
+
+//2、join()让当前线程等待指定线程结束后再继续
+Thread worker = new Thread(() -> {
+    for (int i = 0; i < 5; i++) {
+        System.out.println(Thread.currentThread().getName() + "工作中--->" + i);
+        try {
+            Thread.sleep(200);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();   // 如果触发异常,就中断当前线程
+        }
+    }
+}, "worker-1");
+worker.start();
+worker.join();   // main线程会等待worker执行完成后继续执行
+
+Thread t = new Thread(() -> { ... sleep(5000) ... }, "worker-2");
+t.start();
+t.join(1000);    // main线程会等待1秒,不等5秒
+```
+
+### interrupt() 中断机制 —— 中断标志 + 主动检查
+
+```java
+// 来源:course/ThreadMethodDemo.java(活动代码,interrupt 演示)
+//3、interrupt() 中断操作,设置中断标志
+Thread t1 = new Thread(() -> {
+    while (true) {
+        //主动检查是否中断--检查中断标志
+        if (Thread.currentThread().isInterrupted()) {
+            System.out.println("检查到中断标志...正常退出");
+            break;
+        }
+        System.out.println("工作.......");
+
+        try {
+            //如果线程正在被 sleep/wait的时候被中断会抛异常并清除标志
+            Thread.sleep(100);
+        } catch (InterruptedException e) {
+            System.out.println("sleep中被中断....");
+            //重新设置中断标志,方便上层能感知
+            Thread.currentThread().interrupt();
+            break;
+        }
+    }
+}, "worker-3");
+
+t1.start();
+Thread.sleep(300);  //让t1先跑一会儿
+t1.interrupt();     //向t1发送中断请求
+```
+
+### interrupt 中断机制核心要点
+
+| 环节 | 说明 |
+|------|------|
+| **interrupt() 只是设标志** | `t1.interrupt()` **不会杀死线程**,只是**设置一个中断标志** |
+| **主动检查 isInterrupted()** | 线程内部用 `Thread.currentThread().isInterrupted()` **主动检查中断标志**,发现被中断则正常退出 |
+| **sleep/wait/join 中被中断** | 线程正处在 sleep / wait / join 时被 interrupt(),会**抛 InterruptedException** 并**清除中断标志** |
+| **catch 后重设标志** | catch 块里调用 `Thread.currentThread().interrupt()` **重新设置中断标志**,方便上层代码感知到中断 |
+
+> **要点**:
+> - **interrupt() 是"温柔"的中断**:只是**设置标志位**,不会像"杀线程"那样强制终止——线程需要**主动检查标志**或**在阻塞方法中收到异常**才会响应
+> - **两种响应方式**:①循环里用 `isInterrupted()` **主动检查**标志(有标志就 break 退出);②线程处于 sleep/wait/join 阻塞时被 interrupt(),会**抛 InterruptedException** 中断当前阻塞
+> - **清除标志的细节**:`InterruptedException` 抛出时**中断标志被清除**——所以 catch 里通常要**再调 interrupt() 重设标志**,让上层 `isInterrupted()` 能感知到中断状态
+> - **演示流程**:t1 启动后无限循环打印 → main `sleep(300)` 让 t1 先跑 → `t1.interrupt()` 发送中断请求 → t1 下次循环 `isInterrupted()` 检测到标志 → 正常退出
+> - **join(timeout) 场景**:`t.join(1000)` 表示 main **最多等 1 秒**,而 worker-2 要睡 5 秒——超时后 main 不等了,继续执行
+
+### interrupt 机制的补充说明(本次更新新增)
+
+`ThreadMethodDemo` 末尾补充了 interrupt 机制的**完整流程总结**,并新增了 `yield()` 的演示代码:
+
+```java
+// 来源:course/ThreadMethodDemo.java(末尾注释,本次更新新增)
+/*
+    interrupt() 并不会杀死线程,而是设置一个中断标志,请求线程自行响应。
+    如果线程处在sleep()/wait()/join()等阻塞状态,收到中断标志后,会抛出异常,并清除中断标志
+    捕获异常后,应该重新调用interrupt()恢复中断标志,否则外层代码会感知不到中断
+    线程自用interrupt()/Thread.interrupt()检查标志后,再决定是否退出。
+*/
+```
+
+> **要点**:
+> - **interrupt() 不杀线程**:只是设置中断标志,**请求线程自行响应**——是否退出由线程自己决定
+> - **阻塞状态响应**:线程处在 `sleep()` / `wait()` / `join()` 等阻塞状态时收到中断标志,会**抛异常**并**清除中断标志**
+> - **catch 后恢复标志**:捕获异常后应**重新调用 `interrupt()` 恢复中断标志**,否则**外层代码感知不到中断**
+> - **自检后决定退出**:线程用 `isInterrupted()` / `Thread.interrupted()` 检查标志后,再决定是否退出
+
+### yield() —— 主动让出 CPU 时间片(本次更新新增)
+
+```java
+// 来源:course/ThreadMethodDemo.java(活动代码,yield 演示)
+//4、yield() 主动让出CPU时间片
+Thread t2 = new Thread(() -> {
+    for (int i = 0; i < 50; i++) {
+        Thread.yield();   // 主动让出CPU时间片,重新参与竞争
+        System.out.println(Thread.currentThread().getName() + "第" + i + "轮");
+    }
+}, "yield线程");
+
+t2.start();
+```
+
+> **要点**:
+> - **yield() 是静态方法**:`Thread.yield()` 让**当前线程**主动让出 CPU 时间片,**重新参与竞争**
+> - **只是建议**:`yield()` 只是**建议**调度器让出时间片,**调度器不一定采纳**——并发场景下别的线程可能立刻又抢到 CPU
+> - **yield 与 sleep 的区别**:`sleep(ms)` 是**限时休眠**(进入 TIMED_WAITING,时间到自动醒);`yield()` 是**让出一次**(回到 RUNNABLE,马上可能又被调度)——都不占用 CPU
+> - **演示逻辑**:yield 线程每轮先 `Thread.yield()` 再打印"第 i 轮",50 轮循环——让出时间片后与其他线程(如 main)交替执行
+
+### Object 类的线程操作方法 —— wait() / notify() / notifyAll()(本次更新新增)
+
+`Demo03` 新增了 **Object 类关于线程操作的方法**——这些方法属于**线程通信 / 同步**的基础(等待与唤醒配对):
+
+```java
+// 来源:course/Demo03.java(注释部分,本次更新新增)
+/*
+    Object类的关于线程操作的方法:
+    让线程等待
+    public final void wait()
+    public final void wait(long timeoutMillis)
+    public final void wait(long timeoutMillis, int nanos)
+    唤醒线程
+    public final native void notify()   唤醒线程
+    public final native void notifyAll()    唤醒所有线程
+*/
+```
+
+### wait / notify 方法速查表
+
+| 方法 | 作用 |
+|------|------|
+| `final void wait()` | **让线程等待**:无限期等待另一个线程的 notify / notifyAll |
+| `final void wait(long timeoutMillis)` | 让线程**限时等待**指定毫秒,超时自动恢复 |
+| `final void wait(long timeoutMillis, int nanos)` | 让线程限时等待(毫秒 + 纳秒精度) |
+| `final native void notify()` | **唤醒**一个正在等待的线程 |
+| `final native void notifyAll()` | **唤醒所有**正在等待的线程 |
+
+> **要点**:
+> - **归属 Object 而非 Thread**:`wait()` / `notify()` / `notifyAll()` 是 **Object 类**的方法——因为"等待/唤醒"针对的是**对象监视器(monitor)**,任何对象都可以作为锁对象调用
+> - **与线程状态呼应**:调用 `wait()` 的线程进入 **WAITING**(对应第 7 节状态表);`wait(timeout)` 进入 **TIMED_WAITING**
+> - **等待/唤醒配对**:`wait()` 让线程等待,`notify()` / `notifyAll()` 唤醒等待的线程——这是**生产者-消费者**等线程通信模式的基础
+> - **依赖锁**:`wait()` / `notify()` 必须在**持有对象锁**的同步块(synchronized)中调用,否则抛 `IllegalMonitorStateException`(后续线程同步课程深入)
+
+---
+
+## 9. 线程安全问题:数据竞争与三大特性(Demo04)
+
+### 概念 —— 为什么多线程会出问题(数据竞争)
+
+**进程和进程之间的数据和内存空间是不共享的、相互独立的;而一个进程中的多个线程之间的数据和内存是共享的**。正因为线程间共享数据,多线程带来的**最大隐患就是数据竞争**:
+
+```java
+// 来源:course/Demo04.java(注释部分)
+/*
+    我们知道进程和进程之间的数据和内存空间是不共享的,是相互独立的。
+    一个进程中的多个线程之间的数据和内存是共享的。
+    那么多线程所带来的最大的隐患就是:数据竞争。
+    当多个线程同时读写同一个共享变量的时候,程序的执行结果将会变得不可预测。有时正确,有时错误。而且错误无法复现。
+*/
+```
+
+> **要点**:
+> - **进程间不共享**:进程与进程的数据、内存空间**相互独立**
+> - **线程间共享**:一个进程内的多个线程**共享数据和内存**
+> - **数据竞争**:多个线程**同时读写同一个共享变量**时,执行结果**不可预测**——有时正确、有时错误,且**错误无法复现**(这正是多线程 bug 最难排查的原因)
+
+### 亲手做一个 Bug —— 卖票问题(SellTicket)
+
+理解线程安全的第一步,就是**亲手制作一个 bug**:模拟 3 个售票窗口(3 个线程)同时卖 100 张票。
+
+```java
+// 来源:course/SellTicket.java
+public class SellTicket implements Runnable {
+    private Integer tickets = 100;   // 一共100张票
+
+    @Override
+    public void run() {
+        while (true) {
+            if (tickets > 0) {       // 多个线程可能同时通过这个判断
+                try {
+                    Thread.sleep(10);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
+                tickets--;           // 减一张票
+            }
+        }
+    }
+}
+```
+
+```java
+// 来源:course/Demo04.java(main 方法)
+public static void main(String[] args) {
+    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();
+}
+```
+
+### 卖票问题运行后的两个经典 Bug
+
+```java
+// 来源:course/Demo04.java(注释部分)
+/*
+    运行后看效果:
+        1、相同的票出现了多次
+        2、出现了负数的票
+     原因:线程执行的随机性(抢占式调度)导致的,可能在卖票的过程中丢失了CPU的执行权,导致出现问题
+     相同的票出现了多次:
+        多个线程同时执行到if (tickets>0)判断通过,这个时候tickets还是同一个值,所以都是打印出 窗口3正在出售第10张票
+     出现了负数的票:
+        tickets已经减到1的时候,多个线程同时通过if判断if (tickets>0),然后一个线程执行tickets--变成0,另一个线程继续执行tickets--变成-1
+     程执行的随机性(抢占式调度)导致某个线程在"判断-输出-减票数"这三步的执行过程中,可能丢失CPU执行权,
+     由另一个线程趁机插入到对共享数据的操作,破坏了数据的完整性。
+*/
+```
+
+| Bug | 现象 | 原因 |
+|-----|------|------|
+| **相同的票多次出现** | 多个线程都打印"窗口3正在出售第10张票" | 多个线程**同时通过 `if (tickets>0)` 判断**,此时 tickets 还是同一个值 |
+| **负数票** | tickets 减到 1 时出现 -1 | tickets 为 1 时多个线程同时通过判断,一个线程 `tickets--` 变 0,另一个再 `tickets--` 变 **-1** |
+
+> **要点**:
+> - **根因是随机性**:抢占式调度下,某个线程在"**判断 → 输出 → 减票数**"三步的执行过程中可能**丢失 CPU 执行权**,另一个线程趁机插入对共享数据(tickets)的操作,**破坏了数据的完整性**
+> - **"判断-操作"不是原子**:`if (tickets>0)` 和 `tickets--` 之间不是一气呵成的,线程随时可能被切换走
+
+### 计数器问题 —— count++ 不是原子操作(Counter)
+
+```java
+// 来源:course/Counter.java
+public class Counter {
+    private Integer count = 0;
+
+    public void increment() {
+        count++;    // 看似是1行,但底层是3步
+        //1、拿到当前count的值
+        //2、count+1
+        //3、自增后的值赋值给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());
+    }
+}
+```
+
+### count++ 的非原子性 —— 读-改-写三步
+
+```java
+// 来源:course/Demo04.java(注释部分)
+/*
+    计数器问题:
+        理论上应该是1000,但是实际上少于1000。
+        为什么结果不对?
+        因为count++ 并不是原子操作(原子操作就是无论是多少步,都是一次性全部成功,要么全部失败回滚)
+        count++,表面上是一行代码,但在JVM中被拆成了3步:
+            1、从内存读取count到寄存器(读)
+            2、在寄存器中加1(改)
+            3、把结果写会到内存(写)
+        当多个线程同时执行 读-改-写 三步 的时候,就可能发生 读到了旧值,用旧值进行计算,覆盖别人写入的结果。
+        导致丢失最新的结果
+*/
+```
+
+> **要点**:
+> - **count++ 不是原子操作**:原子操作指"无论多少步,**一次性全部成功或全部失败回滚**"
+> - **count++ 被拆成 3 步**:①**读**(内存 → 寄存器)②**改**(寄存器 +1)③**写**(结果写回内存)
+> - **丢失最新结果**:多个线程同时执行"读-改-写"三步时,可能**读到旧值、用旧值计算、覆盖别人写入的结果**——导致 `count` 理论 1000、实际**小于 1000**
+
+### 多线程安全问题引发的 3 个原因(缺一不可)
+
+```java
+// 来源:course/Demo04.java(注释部分)
+/*
+    多线程安全问题引发原因(缺一不可):
+        1、多线程环境,至少两条路径在并发执行。
+        2、存在共享数据,多个线程访问同一个变量/对象。
+        3、有多条语句操作共享数据,并且这些语句之间存在 读-改-写 的符合操作,比如count++,比如ticket--等
+*/
+```
+
+| # | 原因 | 说明 |
+|---|------|------|
+| 1 | **多线程环境** | **至少两条执行路径**在并发执行 |
+| 2 | **存在共享数据** | 多个线程访问**同一个变量 / 对象** |
+| 3 | **读-改-写复合操作** | 有多条语句操作共享数据,且语句间存在**读-改-写**复合操作(如 `count++`、`tickets--`) |
+
+### 解决多线程安全问题的基本思想
+
+```java
+// 来源:course/Demo04.java(注释部分)
+/*
+    解决多线程安全性问题的基本方案思想:
+        让程序不再具备产生安全问题的环境:
+            1、把多条操作共享数据的语句锁起来。
+            2、让任意时刻只有一个线程能执行这段代码,其他线程只能排队等待。
+*/
+```
+
+> **要点**:
+> - **思路是"消除产生安全问题的环境"**:不满足 3 个原因之一就不会出问题
+> - **方案一**:把多条操作共享数据的语句**锁起来**(synchronized / Lock)
+> - **方案二**:**任意时刻只有一个线程**能执行这段代码,其他线程**排队等待**
+
+### 线程安全三大特性(核心,必须背)
+
+```java
+// 来源:course/Demo04.java(注释部分)
+/*
+    线程安全的三大特性,必须背下来 多线程的核心
+
+        原子性:操作不可分割,要么全部执行,要么全部不执行。防止 读-改-写 被并发打断。
+            |-解决方案:synchronized、Lock(锁)、Atomic类(原子类)
+        可见性:一个线程的修改对另一个线程立即可见。防止线程把变量换成在CPU缓存中,其他线程看不到修改。
+            |-解决方案:volatile、synchronized、Lock(锁)
+        有序性:代码按照书写顺序执行,不会被指令重排。防止编译器和CPU在执行的可能会对指令进行重排。
+            |-解决方案:volatile、synchronized
+    原可序(原子性、可见性、有序性),但凡是线程安全方案,最终都是围绕这三个特性的。
+*/
+```
+
+| 特性 | 含义 | 解决的问题 | 解决方案 |
+|------|------|-----------|----------|
+| **原子性** | 操作**不可分割**,要么全部执行、要么全部不执行 | 防止"**读-改-写**"被并发打断 | `synchronized`、`Lock`(锁)、`Atomic` 类(原子类) |
+| **可见性** | 一个线程的**修改对另一个线程立即可见** | 防止线程把变量缓存到 CPU 缓存中,其他线程看不到修改 | `volatile`、`synchronized`、`Lock`(锁) |
+| **有序性** | 代码**按书写顺序执行**,不会被**指令重排** | 防止编译器和 CPU 对指令重排 | `volatile`、`synchronized` |
+
+> **要点**:
+> - **三大特性口诀**:原子性、可见性、有序性(记法"原可序")——**但凡线程安全方案,最终都是围绕这三个特性的**
+> - **原子性**:防"读-改-写"被打断 → 锁 / 原子类
+> - **可见性**:防 CPU 缓存导致的修改不可见 → volatile / 锁
+> - **有序性**:防指令重排 → volatile / synchronized
+> - 卖票问题和计数器问题正是**原子性**被破坏的典型案例(读-改-写三步被打断)
+
+---
+
+## 10. synchronized 线程同步:三种写法与锁机制(SynchronizedDemo)
+
+### 概念 —— synchronized 是保证原子性最简单可靠的手段
+
+```java
+// 来源:course/Demo04.java(注释部分,本次更新新增)
+/*
+    保证原子性:
+        最简单的最可靠的同步手段保证原子性:synchronized(线程同步)
+        synchronized(线程同步)是Java内置的关键字,也是使用最广泛的同步手段。
+        每个Java对象在底层都对应一把监视器锁(Monitor Lock),当线程进入到synchronized修饰的代码块之前,必
+        须要先获取这个锁。如果这个锁已经被其他线程拿到了,那么当前现在就会进入到BLOCKED(阻塞)状态排队等待。
+        持有锁的线程会执行完代码然后自动释放锁,在等待队列中的线程会重新开始进行新一轮的竞争。
+*/
+```
+
+| 项目 | 说明 |
+|------|------|
+| **synchronized 定位** | Java 内置关键字,保证原子性**最简单最可靠**的同步手段,使用最广泛 |
+| **监视器锁(Monitor Lock)** | 每个 Java 对象底层都对应一把监视器锁 |
+| **获取锁** | 线程进入 synchronized 代码块**之前必须先获取锁** |
+| **锁被占用** | 当前线程进入 **BLOCKED(阻塞)** 状态排队等待 |
+| **释放锁** | 持有锁的线程执行完代码**自动释放锁**,等待队列中的线程**重新竞争** |
+
+> **要点**:
+> - **synchronized 是关键字**:Java 内置的同步手段,不是方法,使用最广泛
+> - **每对象一把监视器锁**:`Monitor Lock` 是 synchronized 底层的锁机制
+> - **BLOCKED 状态呼应**:拿不到锁的线程进入 **BLOCKED**(对应第 7 节线程状态的"阻塞——等待获取监视器锁进入同步块")
+
+### synchronized 的优点与弊端
+
+```java
+// 来源:course/Demo04.java(注释部分)
+/*
+    synchronized优点:
+        解决了多线程的数据安全问题,保证了同一时刻只有一个线程能执行被保护的代码。
+    synchronized弊端:
+        当线程很多的时候,每个线程都有去竞争同一把锁,比较耗资源,无形当中就会降低程序的运行效率。
+        所以锁的粒度应该尽量小,只锁真正操作共享数据的代码,而不是整个方法。
+
+    synchronized修饰方法,该方法会变为同步方法。
+    synchronized修饰代码块,会变成同步代码块。
+*/
+```
+
+| 维度 | 说明 |
+|------|------|
+| **优点** | 解决多线程数据安全问题,保证**同一时刻只有一个线程**能执行被保护的代码 |
+| **弊端** | 线程多时**都去竞争同一把锁**比较耗资源,**降低程序运行效率** |
+| **优化方向** | **锁的粒度尽量小**——只锁真正操作共享数据的代码,而不是整个方法 |
+
+### synchronized 的三种写法(SynchronizedDemo)
+
+```java
+// 来源:course/SynchronizedDemo.java
+public class SynchronizedDemo {
+    private Integer count = 0;
+
+    //写法1---同步实例方法,锁的是当前的对象 this
+    public synchronized void increment() {
+        count++;
+    }
+
+    //写法2---同步静态方法(锁的是当前类的Class对象)
+    private static int staticCount = 0;
+    public static synchronized void staticIncrement() {
+        staticCount++;
+    }
+
+    //写法3---同步代码块(锁的是指定的对象,最灵活)
+    private final Object lock = new Object();
+    public void increment03() {
+        synchronized (lock) {    //只锁这一个代码块,粒度更细,性能更好
+            count++;
+        }
+    }
+}
+```
+
+### 三种写法对照表
+
+| 写法 | 示例 | 锁的是什么 | 特点 |
+|------|------|-----------|------|
+| **同步实例方法** | `public synchronized void increment()` | 当前对象 **this** | 锁住同一个对象的方法调用 |
+| **同步静态方法** | `public static synchronized void staticIncrement()` | 当前类 **Class 对象** | 锁住所有该类的静态调用,**全局唯一** |
+| **同步代码块** | `synchronized(lock) { ... }` | **指定的任意对象** obj | **最灵活**,只锁需要保护的部分,粒度最细、性能更好 |
+
+### synchronized 锁的到底是什么(Demo04 注释详解)
+
+```java
+// 来源:course/Demo04.java(注释部分,本次更新新增)
+/*
+    synchronized锁的是什么?
+
+        public synchronized void method01(){
+            //假如300行代码,只有3行是修改共享数据的。那么先锁的是整个方法的300代码,粒度就太大了。
+        }
+    synchronized修饰的方法也就是同步方法。它锁的其实是当前的对象(this),锁住同一个对象的方法调用
+
+        public static synchronized void method01(){
+            //假如300行代码,只有3行是修改共享数据的。那么先锁的是整个方法的300代码,粒度就太大了。
+        }
+    synchronized修饰的静态方法也就是同步静态方法,它锁的其实是当前对象所属的类的Class对象。锁住的所有这个类的静态调用,全局唯一。
+
+        Object obj=new Object();
+        synchronized(obj){
+            //这个synchronized代码块只锁 那几行修改共享数据的代码,这样粒度就小很多。
+        }
+    synchronized修饰的代码块,锁的是指定的任意对象obj,更加灵活,只锁需要保护的部分。
+*/
+```
+
+```java
+// 来源:course/Demo04.java(新增方法示例)
+public synchronized void method01() {
+    //假如300行代码,只有3行是修改共享数据的。那么先锁的是整个方法的300代码,粒度就太大了。
+}
+
+public void method02() {
+    Object obj = new Object();
+    synchronized (obj) {
+        //这个synchronized代码块只锁 那几行修改共享数据的代码,这样粒度就小很多。
+    }
+}
+```
+
+> **要点**:
+> - **同步实例方法锁 this**:锁的是**当前对象 this**,锁住同一个对象的方法调用
+> - **同步静态方法锁 Class 对象**:锁的是**当前类所属的 Class 对象**,全局唯一——锁住所有该类的静态调用
+> - **同步代码块锁指定对象**:锁的是**指定的任意对象 obj**,更灵活,只锁需要保护的部分
+> - **锁粒度意识**:一个方法 300 行只有 3 行改共享数据——同步方法会锁整个 300 行(粒度太大);同步代码块只锁那 3 行(粒度小、性能好)
+
+### 模拟业务场景 —— 转账(检查-修改必须整体锁住)
+
+```java
+// 来源:course/SynchronizedDemo.java
+public class SynchronizedDemo {
+    //模拟业务场景--转账(多个变量要保持一致)
+    private int balance = 100;
+
+    public void transfer(int amount) {
+        //必须要把 检查-修改 这个过程锁住(同步)
+        synchronized (this) {
+            if (balance >= amount) {
+                balance -= amount;
+                System.out.println("转账" + amount + "成功,余额:" + balance);
+            } else {
+                System.out.println("余额不足");
+            }
+        }
+    }
+}
+```
+
+> **要点**:
+> - **转账场景**:`if (balance >= amount)`(检查)+ `balance -= amount`(修改)**必须整体锁住**——否则多个线程同时"检查"通过后,余额可能被透支
+> - **synchronized(this)**:用当前对象作为锁,保护 balance 的"检查-修改"过程
+> - 这正对应第 9 节"解决基本思想":把操作共享数据的语句**锁起来**、同一时刻只有一个线程执行
+
+### main 演示 —— 加锁后 count 正确性验证
+
+```java
+// 来源:course/SynchronizedDemo.java(main 方法)
+public static void main(String[] args) throws InterruptedException {
+    SynchronizedDemo sd = new SynchronizedDemo();
+
+    //1000个线程同时调用同步方法   调用的是同步方法,必然是1000
+    Thread[] threads = new Thread[1000];
+    for (int i = 0; i < 1000; i++) {
+        threads[i] = new Thread(sd::increment03);
+        threads[i].start();
+    }
+
+    for (Thread t : threads) {
+        t.join();
+    }
+    System.out.println("加锁后最终 count=" + sd.count);
+}
+```
+
+> **要点**:
+> - **与 Counter 对比**:第 9 节 `Counter` 用 1000 线程对**无锁**的 `count++` 并发,count 往往**小于 1000**;这里 1000 线程调用**加了 synchronized 的 `increment03()`**,count **必然是 1000**
+> - **join 等待全部线程**:`t.join()` 等所有线程执行完再打印结果
+> - **方法引用**:`sd::increment03` 是方法引用(回顾 08-04 函数式编程),等价于 `() -> sd.increment03()`
+> - **同一对象加锁才有效**:所有线程用的是**同一个 `sd` 对象**的 lock——若每个线程 new 一个 SynchronizedDemo,锁对象不同就失效了
+
+---
+
+## 11. 随堂练习:三种创建线程方式回顾 + 线程常用方法(Exercise01)
+
+### 练习要求 —— 把多线程三种实现方式与常用方法串起来
+
+`Exercise01` 是随堂练习的**骨架代码**,题目要求用三种方式创建并使用线程,并调用线程常用方法:
+
+```java
+// 来源:exericse/Exercise01.java(注释部分)
+public static void main(String[] args) {
+    //1、使用继承Thread方式创建线程并使用线程
+    //2、使用runnable方式创建并使用线程
+    //3、使用callable方式创建并使用线程
+
+    /*
+    调用一些方法
+        获取当前线程名称的方法
+        设置当前线程名称的方法
+        获取线程返回结果的方法
+    */
+}
+```
+
+### 三种创建线程方式速查表(回顾 20260808)
+
+| 方式 | 核心动作 | 返回结果 |
+|------|----------|----------|
+| **方式一:继承 Thread** | 自定义类 `extends Thread` + 重写 `run()` + `start()` 启动 | `run()` 无返回值 |
+| **方式二:实现 Runnable** | 实现类 `implements Runnable` + 重写 `run()` + `new Thread(runnable, "名")` | `run()` 无返回值 |
+| **方式三:实现 Callable** | 实现类 `implements Callable<V>` + 重写 `call()` + `new FutureTask<>(callable)` + `new Thread(futureTask)` | `call()` **有返回值** |
+
+### 练习要求调用的线程方法
+
+| 方法 | 归属 | 作用 |
+|------|------|------|
+| **获取当前线程名称** | `Thread.currentThread().getName()` | 静态方法拿当前线程对象,再取名字 |
+| **设置当前线程名称** | `thread.setName(String)` | 动态修改线程名 |
+| **获取线程返回结果** | `futureTask.get()` | 拿到 Callable 线程 `call()` 的返回值 |
+
+> **要点**:
+> - 这是把 20260808 学过的**三种多线程实现方式**做一次综合练习:继承 Thread、实现 Runnable、实现 Callable 各写一个线程任务
+> - 三个"常用方法"对应三种能力:**取线程名**(getName)、**改线程名**(setName)、**取返回结果**(get)——分别覆盖了方式二/三中"不继承 Thread 时如何定位线程"以及"Callable 如何拿返回值"的要点
+> - 与 Demo01 呼应:Demo01 已经用了 `Thread.currentThread().getName()`(取线程名)和 `setPriority()`(改优先级),练习还要补上 `setName()` 与 `get()`
+
+### 随堂练习完成版 —— CooperationTest(join + yield + interrupt 综合)
+
+`CooperationTest`(`exericse` 包)是本日**随堂练习的完成版**,把第 8 节的线程操作方法 join / yield / interrupt 串成一个完整演示——worker-1 用 `yield()` 让出 CPU 并配合 `join()` 等待,worker-2 用 `interrupt()` 中断长 sleep 任务:
+
+```java
+// 来源:exericse/CooperationTest.java
+public static void main(String[] args) throws InterruptedException {
+    // ========== 1、join + yield 演示 ==========
+    Thread worker = new Thread(() -> {
+        for (int i = 0; i < 5; i++) {
+            Thread.yield();   // 主动让出 CPU 时间片
+            System.out.println(Thread.currentThread().getName() + "工作中--->" + i);
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();   // 重设中断标志
+                break;
+            }
+        }
+    }, "worker-1");
+
+    worker.start();
+    worker.join();   // 主线程等待 worker-1 结束
+    System.out.println(worker.getName() + "已结束,主线程继续...");
+
+    // ========== 2、interrupt 中断演示 ==========
+    Thread sleeper = new Thread(() -> {
+        try {
+            Thread.sleep(5000);   // 长任务:睡 5 秒
+            System.out.println("任务完成");   // 被中断就不会执行到这
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();   // 重设中断标志
+            System.out.println("sleep 中被中断,isInterrupted()=" + Thread.interrupted());
+        }
+    }, "worker-2");
+
+    sleeper.start();
+    Thread.sleep(500);       // 让 sleeper 先进入 sleep 状态
+    sleeper.interrupt();     // 向 worker-2 发送中断请求
+    sleeper.join();          // 主线程等 worker-2 处理完中断
+    System.out.println("主线程结束...");
+}
+```
+
+> **要点**:
+> - **join 等待**:`worker.join()` 让主线程等 worker-1 跑完 5 轮再继续;`sleeper.join()` 等 worker-2 处理完中断
+> - **yield 让出**:worker-1 每轮先 `Thread.yield()` 主动让出 CPU,与其他线程交替执行
+> - **interrupt 中断**:worker-2 正在 sleep(5000) 长任务,主线程 sleep(500) 后 `interrupt()`,worker-2 在 sleep 中被中断抛 `InterruptedException`,catch 里重设标志后打印中断信息
+> - **细节注意**:代码中打印用的是 `Thread.interrupted()`(静态方法,检查并**清除**中断标志)——与 `isInterrupted()`(只检查不清除)对比理解
+
+---
+
+## 12. 课后作业回顾:Callable + FutureTask 带返回值求和(Homework_03)
+
+### 作业背景
+
+`homework0808/Homework_03.java`(本次由 `homework_03` 重命名、类名规范化为 `Homework_03`,逻辑不变)是 8 月 8 日课后作业的第三题:**用方式三(实现 Callable 接口 + FutureTask)创建 3 个线程,分别求 1~100 / 1~1000 / 1~10000 的和,并通过 `get()` 获取返回值**。与本日的"线程操作方法、三种实现方式综合练习"呼应。
+
+### ① 实现 Callable 接口:泛型指定返回类型
+
+```java
+// 来源:homework0808/Homework_03.java
+static class SumCallable implements Callable<Integer> {
+    private int n;   // 求 1~n 的和
+
+    public SumCallable(int n) {
+        this.n = n;
+    }
+
+    @Override
+    public Integer call() throws Exception {
+        int sum = 0;
+        for (int i = 1; i <= n; i++) {
+            sum += i;
+        }
+        System.out.println(Thread.currentThread().getName() + "求和结果:" + sum);
+        return sum;   // 返回计算结果(get() 会拿到这个值)
+    }
+}
+```
+
+> **要点**:
+> - **`Callable<Integer>` 泛型** = `call()` 的返回值类型(求 int 和所以用 `Integer` 包装类)
+> - **`call()` 是带返回值**的线程任务方法,且声明 `throws Exception`(对比 Runnable 的 `run()` 无返回值)
+> - **任务携带参数**:`SumCallable(int n)` 通过构造方法把求和上限 n 传入任务对象——任务对象可以"携带数据"
+> - **`Thread.currentThread().getName()`**:方式三不继承 Thread,用静态方法获取当前线程名
+
+### ② FutureTask 包装 + 3 个线程启动 + get() 获取返回值
+
+```java
+// 来源:homework0808/Homework_03.java(main 方法核心逻辑)
+public static void main(String[] args) throws Exception {
+    // 1、创建 3 个 SumCallable 对象(求 1~100 / 1~1000 / 1~10000 的和)
+    SumCallable sc1 = new SumCallable(100);
+    SumCallable sc2 = new SumCallable(1000);
+    SumCallable sc3 = new SumCallable(10000);
+
+    // 2、用 FutureTask<Integer> 包装每个 Callable 对象
+    FutureTask<Integer> ft1 = new FutureTask<>(sc1);
+    FutureTask<Integer> ft2 = new FutureTask<>(sc2);
+    FutureTask<Integer> ft3 = new FutureTask<>(sc3);
+
+    // 3、new Thread(futureTask, "线程名") 创建 3 个线程并 start()
+    Thread t1 = new Thread(ft1, "线程1");
+    Thread t2 = new Thread(ft2, "线程2");
+    Thread t3 = new Thread(ft3, "线程3");
+    t1.start();
+    t2.start();
+    t3.start();
+
+    // 4、调用 ft.get() 获取每个线程的返回值,打印并在 main 中汇总总结果
+    System.out.println(ft1.get());
+    System.out.println(ft2.get());
+    System.out.println(ft3.get());
+
+    // 5、(思考)对比三种实现方式:run() 无返回值 vs call() 有返回值,各自适合什么场景
+}
+```
+
+### 完整调用链
+
+```
+SumCallable(100) → FutureTask<Integer>(sc1) → new Thread(ft1, "线程1") → t1.start()
+                                                                    ↓ 异步执行 call()
+ft1.get()  ←————— 等待线程1计算完成,返回 call() 的返回值(5050)
+```
+
+> **要点**:
+> - **FutureTask 是"中间人"**:Callable 不能直接交给 Thread,用 `FutureTask<Integer>` 包装后传给 Thread 构造方法(Thread 构造只接收 Runnable / FutureTask)
+> - **每个线程独立求和**:sc1/sc2/sc3 三个任务对象,分别算 1~100、1~1000、1~10000 的和,各自包装成 ft1/ft2/ft3
+> - **指定线程名**:`new Thread(ft1, "线程1")` 第二个参数指定线程名,`call()` 里 `Thread.currentThread().getName()` 就能拿到它
+> - **get() 阻塞等待 + 拿返回值**:`ft1.get()` 会等待线程1计算完成,返回其 `call()` 的返回值(5050),打印结果
+> - **main 抛异常**:`throws Exception` 统一处理 `call()` / `get()` 抛出的受检异常
+> - **三种方式对比(作业思考题)**:`run()` 无返回值,适合只执行任务、不需要结果;`call()` 有返回值,适合"计算完要把结果拿回来用"的场景(如求和、查询、累加)
+
+---
+
+## 13. 知识点全景总结
+
+本课知识点围绕"**线程操作方法 + 守护线程 + 线程生命周期 + 线程安全问题 + synchronized 线程同步 + 多线程实现方式综合应用**"展开,一张表看清"知识 → 代码位置":
+
+| 知识点 | 具体体现 | 源码位置 |
+|--------|----------|----------|
+| **Thread.sleep(毫秒)** | 静态方法,让当前线程休眠指定毫秒数;抛 InterruptedException 需处理 | course/Demo01 |
+| **sleep 的应用** | 循环中每 100 毫秒打印一次,放慢执行、让出时间片,便于观察并发交替 | course/Demo01 main |
+| **线程调度方式** | 分时调度(轮流 + 平均分配时间片)vs 抢占式调度(优先高优先级、同优先级随机选);**Java 使用抢占式调度** | course/Demo01 注释 |
+| **多线程执行的随机性** | 单 CPU 同一时刻只能执行一条指令;线程抢到时间片才执行;谁抢到不确定 → 输出顺序随机 | course/Demo01 注释 |
+| **getPriority()** | 返回此线程的优先级(默认 5) | course/Demo01 |
+| **setPriority(int)** | 修改线程优先级,范围 1~10 | course/Demo01 |
+| **优先级与调度联动** | 抢占式调度下优先级高的线程获取 CPU 时间片更多 | course/Demo01 注释 |
+| **Lambda 创建 Runnable** | `Runnable run = () -> {...}` 作为任务对象,可同时给多个 Thread 使用(回顾方式二) | course/Demo01 main |
+| **双线程并发演示** | 同一个 Runnable 创建 t1 / t2 两个线程,start() 后 run() 交替并发执行 | course/Demo01 main |
+| **线程默认名** | 未指定名字时默认 Thread-0 / Thread-1 | course/Demo01 main |
+| **守护线程概念** | 守护线程是随其他非守护线程结束而结束的服务型线程;用户线程全部结束 JVM 才退出 | course/Demo02 注释 |
+| **setDaemon(boolean)** | 将当前线程标记为守护线程;当运行的线程都是守护线程时 JVM 退出 | course/Demo02 注释 |
+| **isDaemon()** | 判断当前线程是否是守护线程 | course/Demo02 注释 |
+| **JVM 退出机制** | 只要有任何一个用户线程存活,JVM 就不退出(main 结束 ≠ JVM 退出)——服务程序"卡着不退"的原因 | course/Demo02 注释 |
+| **守护线程经典场景** | GC 垃圾回收线程最典型;心跳检测、后台日志、监控统计等后台支撑任务 | course/Demo02 注释 |
+| **守护线程四大特点** | ①setDaemon 必须在 start() 前(否则 IllegalThreadStateException)②守护线程创建的子线程默认也是守护线程 ③JVM 强杀时 finally 不一定执行、不能用于资源清理 ④isDaemon() 判断 | course/Demo02 注释 |
+| **守护线程演示** | MyThread01(sleep 1000ms 循环打印)创建"线程1"普通线程 + "守护线程";`setDaemon(true)` **在 start() 之前**设置(本次更新修正,保证合法生效) | course/Demo02 main |
+| **MyThread01/02 抽取独立文件** | 原 Demo02 内部静态类抽取为独立外部类 `MyThread01` / `MyThread02`(代码重构,逻辑不变) | course/MyThread01、MyThread02 |
+| **线程生命周期概念** | 线程从生到死的过程:新建、就绪、运行、死亡,运行中可能进入阻塞 | course/Demo03 注释 |
+| **线程 6 种状态(Thread.State 枚举)** | NEW(new 未 start)/ RUNNABLE(start 后运行或等时间片)/ BLOCKED(抢监视器锁失败)/ WAITING(wait/join/park 无限期等)/ TIMED_WAITING(sleep/wait(timeout)/join(timeout) 限时等)/ TERMINATED(run 结束) | course/Demo03 注释 |
+| **状态转换规律** | RUNNABLE 是**总枢纽**(阻塞状态都有办法回到 RUNNABLE);TERMINATED 是**终点站**(不可逆) | course/Demo03 注释 |
+| **操作系统 5 状态 vs Java 6 状态** | OS 分新建/就绪/运行/阻塞/终结 5 种;Java 把就绪+运行合并为 RUNNABLE,且细分 WAITING / TIMED_WAITING | course/Demo03 注释 |
+| **join() / join(long)** | `t.join()` 当前线程等待 t 执行完毕;`join(millis)` 最多等待指定毫秒(超过不等)——调用方进入 WAITING / TIMED_WAITING | course/Demo03、ThreadMethodDemo |
+| **interrupt() 中断机制** | 只是**设置中断标志**不杀线程;`isInterrupted()` 主动检查;sleep/wait/join 中被中断抛 InterruptedException 并**清除标志**,catch 后通常**重设标志** | course/ThreadMethodDemo |
+| **interrupt 机制补充说明** | interrupt 请求线程**自行响应**;阻塞状态收到中断标志抛异常并清除标志;catch 后**重新 interrupt() 恢复标志**;线程自检标志后再决定退出 | course/ThreadMethodDemo 末尾注释 |
+| **yield() 主动让出 CPU** | 静态方法,让当前线程主动让出 CPU 时间片重新竞争;**只是建议**调度器不一定采纳;与 sleep 区别(yield 让一次回到 RUNNABLE,sleep 限时进 TIMED_WAITING) | course/Demo03 注释、ThreadMethodDemo |
+| **isAlive() 判断存活** | 判断线程是否存活:已执行 start() 但未结束;TERMINATED 后返回 false | course/Demo03 注释 |
+| **Object 类线程方法 wait/notify** | `wait()` / `wait(timeout)` / `wait(timeout,nanos)` 让线程等待(进 WAITING / TIMED_WAITING);`notify()` 唤醒一个 / `notifyAll()` 唤醒所有——线程通信基础,依赖对象锁 | course/Demo03 注释 |
+| **Demo03 main 演示** | 4 个 MyThread01 线程 + main 并发打印,最后 `mt2.join()` 让 main 等待线程2结束 | course/Demo03 main |
+| **ThreadMethodDemo 综合演示** | sleep(TIMED_WAITING)/ join(main 等 worker)/ join(1000)(最多等 1 秒)/ interrupt(t1 循环检查标志退出)/ yield(yield 线程每轮先让出 CPU 再打印) | course/ThreadMethodDemo |
+| **数据竞争概念** | 进程间数据不共享、线程间共享;多线程同时读写同一共享变量 → 结果不可预测、错误无法复现 | course/Demo04 注释 |
+| **卖票问题(SellTicket)** | 3 窗口线程卖 100 张票;相同票多次出现(多线程同时通过 if 判断)+ 负数票(tickets-- 连续执行) | course/SellTicket、Demo04 main |
+| **计数器问题(Counter)** | count++ 不是原子操作,JVM 拆成"读-改-写"3 步;多线程并发时读到旧值覆盖新值,理论 1000 实际小于 1000 | course/Counter |
+| **多线程安全问题 3 原因** | ①多线程环境 ②存在共享数据 ③多条语句操作共享数据且有读-改-写复合操作(缺一不可) | course/Demo04 注释 |
+| **解决基本思想** | 把操作共享数据的语句**锁起来**;**任意时刻只有一个线程**执行,其他线程排队等待 | course/Demo04 注释 |
+| **线程安全三大特性** | **原子性**(防读-改-写打断→synchronized/Lock/Atomic)、**可见性**(防 CPU 缓存→volatile/锁)、**有序性**(防指令重排→volatile/synchronized);"原可序"是线程安全核心 | course/Demo04 注释 |
+| **synchronized 与监视器锁** | Java 内置关键字,保证原子性最简单可靠手段;每个对象底层对应一把监视器锁(Monitor Lock),进 synchronized 前必须拿锁,拿不到进 **BLOCKED** 排队,持有线程执行完自动释放 | course/Demo04 注释 |
+| **synchronized 优点 / 弊端** | 优点:同一时刻只有一个线程执行被保护代码;弊端:线程多时竞争同一把锁耗资源、降效率 → **锁粒度尽量小**,只锁共享数据代码 | course/Demo04 注释 |
+| **同步实例方法(写法1)** | `public synchronized void increment()` 锁的是当前对象 **this**,锁住同一对象的方法调用 | course/SynchronizedDemo |
+| **同步静态方法(写法2)** | `public static synchronized void staticIncrement()` 锁的是**类的 Class 对象**,全局唯一 | course/SynchronizedDemo |
+| **同步代码块(写法3)** | `synchronized(lock) { ... }` 锁**指定的任意对象**,最灵活、粒度最细、性能更好 | course/SynchronizedDemo |
+| **转账业务场景** | `synchronized(this)` 把"检查-修改"(balance>=amount 判断 + balance-=amount)整体锁住,防止并发透支 | course/SynchronizedDemo |
+| **加锁后 count 验证** | 1000 线程调用加锁的 increment03(),count **必然是 1000**(对比 Counter 无锁时小于 1000);方法引用 `sd::increment03` | course/SynchronizedDemo main |
+| **CooperationTest 练习完成版** | join(主线程等 worker-1)/ yield(每轮让出 CPU)/ interrupt(中断 sleep 长任务,catch 重设标志);打印用 Thread.interrupted()(检查并清除标志) | exericse/CooperationTest |
+| **随堂练习:三种创建线程方式** | 继承 Thread / 实现 Runnable / 实现 Callable 各写一个线程任务 | exericse/Exercise01 |
+| **线程常用方法** | 获取当前线程名 getName / 设置线程名 setName / 获取返回结果 get | exericse/Exercise01 |
+| **实现 Callable 接口** | `implements Callable<Integer>`,泛型指定返回类型;重写 `call()` 带返回值 | homework0808/Homework_03 |
+| **FutureTask 包装 Callable** | `new FutureTask<>(callable)` 包装任务,传给 `new Thread(futureTask, "线程名")` | homework0808/Homework_03 |
+| **get() 获取线程返回值** | `ft.get()` 阻塞等待该线程 call() 计算完成,返回结果 | homework0808/Homework_03 |
+| **多线程独立求和** | 三个 SumCallable 分别求 1~100 / 1~1000 / 1~10000 的和,各自线程并行计算 | homework0808/Homework_03 |
+| **三种实现方式适用场景** | run() 无返回值(只执行任务);call() 有返回值(需拿回计算结果) | homework0808/Homework_03 注释 |
+| **作业类名规范化** | `homework_03` 重命名为 `Homework_03`(类名遵循大驼峰规范),逻辑不变 | homework0808/Homework_03 |
+
+---
+
+## 14. 随堂练习要点
+
+- **加锁后 count 验证实验**:运行 `SynchronizedDemo`,观察加锁后 1000 线程 count **必然是 1000**;与第 9 节 `Counter`(无锁 count 往往小于 1000)对比,直观体会 synchronized 解决原子性问题的效果
+- **三种写法改造实验**:把 `SynchronizedDemo` 的 `increment03`(同步代码块)改成同步实例方法 / 同步静态方法,分别运行验证结果;对比三种写法在"锁的对象"上的差异(this / Class 对象 / 指定对象)
+- **锁粒度实验**:给 `transfer()` 中的"检查-修改"去掉 synchronized 再运行多次,观察余额是否出现透支(负数);加上 synchronized(this) 后验证正确——体会"检查-修改必须整体锁住"
+- **卖票问题加锁修复实验**:给 `SellTicket.run()` 中操作 tickets 的代码块加 `synchronized(this)`,再次运行观察相同票和负数票是否消失(回顾第 9 节的预演,本次正式验证)
+- **BLOCKED 状态观察**:在线程 run() 中进入 synchronized 代码块前后打印 `getState()`,让多个线程竞争同一把锁,观察抢锁失败线程是否处于 **BLOCKED** 状态
+- **不同锁对象失效实验**:把 `SynchronizedDemo` main 中 `sd::increment03` 改成每次 `new SynchronizedDemo()` 再调用 increment03,观察 count 是否不再等于 1000——体会"锁对象必须一致才有效"
+- **卖票问题复现实验**:运行 `Demo04`(3 个售票窗口线程),多运行几次,观察**相同的票多次出现**和**负数票**两个 bug——体会数据竞争(每次结果都可能不同、错误无法复现)
+- **计数器问题复现实验**:运行 `Counter`(1000 个线程同时 count++),多次运行观察最终 count **往往小于 1000**——体会 count++ 的"读-改-写"非原子性
+- **缩小并发窗口实验**:把 `SellTicket` 的 `Thread.sleep(10)` 去掉或加大到 100,观察卖票 bug 出现的频率变化——sleep 越大(并发窗口越大),bug 越容易复现
+- **线程安全解决预演**:试着给 `SellTicket.run()` 中操作 tickets 的代码块加 `synchronized(this) { ... }`,再次运行观察相同票和负数票是否消失——提前体验"锁"的作用(后续课程正式学习)
+- **三个原因分析练习**:对照"多线程安全问题 3 原因",分析卖票问题和计数器问题分别满足哪 3 个条件(缺一不可),加深理解
+- **三大特性识记**:背下"原子性、可见性、有序性(原可序)"及其对应解决方案(原子性→synchronized/Lock/Atomic;可见性→volatile/锁;有序性→volatile/synchronized)
+- **CooperationTest 练习**:补全 `exericse/CooperationTest` 中的 TODO,运行验证 join / yield / interrupt 三种方法的协作效果;把打印处 `Thread.interrupted()` 换成 `isInterrupted()`,对比两种方法在"检查后是否清除标志"上的差异
+- **yield 让出 CPU 实验**:运行 `ThreadMethodDemo`,观察"yield线程"打印"第 i 轮"与 main 等其他线程输出的交错情况;把 `Thread.yield()` 注释掉再运行对比,体会 yield 主动让出 CPU 时间片的效果
+- **yield vs sleep 对比**:把 `Thread.yield()` 换成 `Thread.sleep(1)` 运行,对比两者对输出交错密度的影响——yield 只是让出一次(可能马上又被调度),sleep 是限时休眠(至少等 1 毫秒)
+- **isAlive 实验**:在 main 中启动子线程后立刻打印 `t.isAlive()`(true),等子线程 run() 结束后再打印(false);对比 NEW(未 start)与 TERMINATED 状态的 isAlive 返回值
+- **wait/notify 入门实验**:写一个简单的生产者-消费者雏形——一个线程对对象调用 `wait()` 等待,另一个线程 `sleep` 后对同一对象调用 `notify()` 唤醒,观察等待线程是否被唤醒继续执行(需在 synchronized 同步块中调用,提前体会线程同步)
+- **wait(timeout) 限时实验**:线程调用 `wait(2000)` 后不调用 notify,观察等待线程是否在 2 秒后**自动恢复**执行——对应第 7 节状态表中 wait(timeout) 进入 TIMED_WAITING
+- **线程生命周期状态打印实验**:在 `MyThread01` 的 run() 不同位置打印 `Thread.currentThread().getState()`,观察线程在 main 中 `new Thread()` 后(NEW)、`start()` 后(RUNNABLE)、`sleep()` 期间(TIMED_WAITING)、`run()` 结束后(TERMINATED)的状态变化
+- **join 等待实验**:运行 `Demo03`,把 `mt2.join()` 注释掉再运行一次,对比两次输出中 main 与线程2 打印顺序的差异——有 join 时 main 会**等待线程2 结束**,无 join 时 main 与线程2 并发交错
+- **join(timeout) 实验**:把 `ThreadMethodDemo` 中 `t.join(1000)` 改成 `t.join()`(无限等),观察 main 是否一直等 worker-2 睡满 5 秒;再改回 `join(1000)`,体会"最多等待指定毫秒,超过就不等"
+- **interrupt 主动检查实验**:运行 `ThreadMethodDemo`,把 `t1.interrupt()` 注释掉,观察线程 t1 是否**一直循环打印**停不下来;恢复 interrupt 后再观察 t1 检测到中断标志正常退出
+- **interrupt 阻塞中断实验**:在线程 run() 中先 `Thread.sleep(10000)`,main 里 `sleep(500)` 后 `t1.interrupt()`——观察 sleep 中抛 `InterruptedException`、catch 里重设标志后 `isInterrupted()` 恢复为 true 的过程
+- **sleep 与 TIMED_WAITING**:在 run() 中 `Thread.sleep(100)` 前后分别打印 `Thread.currentThread().getState()`,验证 sleep 期间线程处于 **TIMED_WAITING**(计时等待)状态
+- **守护线程设置时机实验**:运行 `Demo02`(代码已把 `setDaemon(true)` 放在 `start()` **之前**,设置合法生效),观察"线程1"(用户线程)持续打印,而"守护线程"在用户线程结束后被 JVM 强制终止;再把 `setDaemon(true)` 故意移回 `mt2.start()` **之后**运行,观察抛 `IllegalThreadStateException`,体会"设置时机"特点
+- **JVM 退出条件实验**:只启动一个普通子线程(不设守护)跑几秒就退出 main,观察**JVM 是否立即退出**——只要用户线程还在跑,JVM 就"卡着不退";再把该线程设为守护线程,观察 main 结束后 JVM 直接退出
+- **isDaemon 判断实验**:在线程 run() 中打印 `Thread.currentThread().isDaemon()`,对比普通线程与守护线程的输出差异(true / false)
+- **守护属性继承实验**:在守护线程的 run() 中再 new 一个子线程并启动,用 `isDaemon()` 打印它的守护属性,验证"守护线程创建的子线程默认也是守护线程"
+- **finally 不保证实验**:在守护线程 run() 中写 try-finally(finally 里打印"清理资源"),让 JVM 在它执行完前退出,观察 finally 是否被执行——体会"守护线程被强杀时 finally 不一定执行,不能用于资源清理"
+- **sleep 实验**:把 `Thread.sleep(100)` 的毫秒数改成 1000 / 10,运行 Demo01,观察两条线程输出的**交错密度**变化——休眠越长切换越明显;再分别注释 sleep 前后的 `start()` 对比
+- **优先级实验**:把 t1 优先级改成 10、t2 保持 5(或改成 1),运行多次,用 `Thread.currentThread().getName()` 统计两个线程输出的条数,观察**高优先级线程获得 CPU 机会是否更多**
+- **getPriority 验证**:在 `new Thread(run)` 前后分别打印 `getPriority()`,验证新线程默认优先级都是 5;再试试 `setPriority(0)` / `setPriority(11)`,观察抛 `IllegalArgumentException`
+- **随机性实验**:反复运行 Demo01 多次,观察每次控制台输出顺序是否一致——体会"谁抢到 CPU 时间片不确定"
+- **run 与 start 对比**:把 `t1.start()` 改成 `t1.run()`,观察是否还有两个线程交替——直接调 run() 不会创建新线程
+- **三种方式综合练习**:仿照 Exercise01 的要求,分别用继承 Thread、实现 Runnable、实现 Callable 各写一个"打印 0~99 并求和"的线程任务,对比三种代码结构
+- **常用方法练习**:在 Runnable / Callable 实现类中,用 `Thread.currentThread().getName()` 打印线程名;用 `new Thread(task, "指定名")` 或 `setName("新名")` 设置线程名,验证 getName 的结果
+- **课后作业运行**:运行 `homework0808/Homework_03`,观察控制台依次打印三个线程各自的求和结果,再通过 `ft1.get()` / `ft2.get()` / `ft3.get()` 拿到返回值,验证三个线程是否并发计算(结果正确、输出顺序不定)
+- **get() 汇总思考**:作业要求"在 main 中汇总总结果"——把 `ft1.get() + ft2.get() + ft3.get()` 相加,打印三种规模求和的**总和**,体会"主线程等待子线程结果并汇总"
+- **三种方式适用场景对比**:思考题——`run()` 无返回值适合"烧水、打印、发消息"这类只执行不看结果的场景;`call()` 有返回值适合"求和、查询数据库、计算成绩"这类**需要拿结果继续处理**的场景
+- **知识点串联**:把 20260808 的"三种实现方式 + 线程调度"与今天的"sleep / 优先级"串成一条线——**创建线程(3 种方式)→ 控制线程(sleep / 优先级)→ 获取结果(get)**
+
+---
+
+## 15. 拓展阅读
+
+- **JDK 文档**:[`java.lang.Thread`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Thread.html)(线程类,`sleep()` / `getPriority()` / `setPriority()` / `getName()` / `setName()` / `currentThread()` / `setDaemon()` / `isDaemon()` / `join()` / `interrupt()` / `isInterrupted()` / `getState()` / `yield()` / `isAlive()` 等核心方法)、[`java.lang.Object`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html)(`wait()` / `notify()` / `notifyAll()` 线程通信方法)、[`java.lang.Thread.State`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Thread.State.html)(线程 6 种状态枚举)
+- **线程安全入门**:多线程的**数据竞争**是并发编程的根源性问题——卖票问题(相同票 / 负数票)和计数器问题(count 小于理论值)是理解线程安全最经典的入门案例,都源于**读-改-写复合操作被并发打断**
+- **原子操作**:`count++` 不是原子操作(读-改-写 3 步);Java 提供 `AtomicInteger` / `AtomicLong` 等**原子类**,其内部方法(如 `incrementAndGet()`)是原子的,无需加锁即可保证线程安全
+- **volatile 关键字**:`volatile` 修饰的变量**不缓存到 CPU 寄存器 / 缓存**,保证**可见性**(一个线程的修改对其他线程立即可见);但它**不能保证原子性**(count++ 用 volatile 依然不安全),需配合锁或原子类
+- **synchronized 预读**:卖票问题的标准解法是 `synchronized` 同步块 / 同步方法——同一时刻只允许一个线程进入临界区,其余线程**排队等待**(对应线程状态中的 **BLOCKED** 状态,抢锁失败被动阻塞)
+- **synchronized 的锁对象选择**:锁必须选**所有线程共享的对象**才有效——同步实例方法锁 this、同步静态方法锁 Class 对象、同步代码块锁指定对象;如果各线程 new 各自对象去加锁(锁对象不一致),synchronized 就**失效**了
+- **synchronized 的可重入性**:`synchronized` 是**可重入锁**——同一线程可以重复获取自己已经持有的锁(如同步方法内再调用另一个同步方法),不会自己把自己锁死;每个监视器锁都关联一个持有计数
+- **synchronized 与 wait/notify**:`wait()` / `notify()` 必须**持有 synchronized 锁**时调用(回顾第 8 节 Object 类方法)——它们本质是"释放锁并等待" / "唤醒等待并重新竞争锁",是 synchronized 锁之上实现的线程通信
+- **指令重排**:编译器和 CPU 为了优化性能可能**重排指令执行顺序**(有序性问题);`volatile` 通过内存屏障禁止重排,`synchronized` 通过锁保证临界区内代码的有序执行
+- **Lock 与 synchronized 对比**:JDK5 引入的 `Lock` 接口(如 `ReentrantLock`)功能更强——可尝试 `tryLock()`(尝试获取不阻塞)、`lockInterruptibly()`(可中断获取锁)、公平锁;但**使用更复杂**(需手动 unlock),synchronized 是内置关键字更简单
+- **debug 多线程 bug 的困难**:数据竞争类 bug **错误无法复现**(每次运行结果可能不同),排查时常用思路是**缩小并发窗口**(加大 sleep 放大 bug)、**线程转储(thread dump)**、**添加日志打印中间状态**
+- **生产环境线程安全**:真实项目中很少手写 synchronized 管理大量线程,而是用**线程池(ExecutorService)** + **并发容器(ConcurrentHashMap / CopyOnWriteArrayList)** + **原子类**等 JDK 提供的线程安全组件
+- **守护线程与 JVM 生命周期**:JVM 在**最后一个非守护线程结束**时才退出(守护线程不阻止退出);`main` 方法本身运行在用户线程上,但 `main` 返回**不代表** JVM 退出——只要还有用户线程存活,JVM 就继续运行(这正是很多服务程序"关不掉"的原因)
+- **setDaemon 的时机限制**:`setDaemon(true)` 必须在 `start()` **之前**调用,否则抛 `IllegalThreadStateException`;线程启动后守护属性不可再修改
+- **守护线程的典型应用**:GC 垃圾回收线程是最经典的守护线程;此外**心跳检测、后台日志、监控统计、自动保存**等"随主程序生灭"的后台任务都适合用守护线程,避免程序退出时被"后台线程卡住"
+- **守护线程 vs 用户线程选型**:需要**程序结束前必须完成**的任务(如写文件、释放连接)用**用户线程**;"可有可无、主程序结束就应终止"的任务用**守护线程**——因为守护线程被强杀时 **finally 不保证执行**,不能依赖它做资源清理
+- **Thread 优先级常量**:Thread 类内置三个优先级常量——`MIN_PRIORITY = 1`、`NORM_PRIORITY = 5`(默认)、`MAX_PRIORITY = 10`;`setPriority()` 参数超出 1~10 范围会抛 `IllegalArgumentException`
+- **sleep 与 yield 的区别**:`Thread.sleep(ms)` 让线程休眠指定毫秒(可被中断,抛 InterruptedException);`Thread.yield()` 只**让出一次 CPU**(回到就绪状态,马上又可能被调度)——都是主动让出 CPU 的手段
+- **wait 与 sleep 的区别(再深化)**:`wait()` 是 Object 方法,**必须持有对象锁**调用,调用后**释放锁**并进入 WAITING,需 `notify()` / `notifyAll()` 唤醒;`sleep()` 是 Thread 静态方法,**不释放锁**,时间到自动醒——两者是"线程通信"与"线程暂停"的分水岭
+- **notify vs notifyAll**:`notify()` 只唤醒**一个**等待该对象的线程(具体哪个由 JVM 决定);`notifyAll()` 唤醒**所有**等待线程——多个线程等待同一条件时优先用 notifyAll,避免"通知丢失"导致部分线程永久等待
+- **wait/notify 的应用**:经典的**生产者-消费者模式**——生产者生产数据后 `notify()`,消费者 `wait()` 等待数据;缓冲区满时生产者等待、缓冲区空时消费者等待,实现线程间的协调与解耦
+- **yield 的适用场景**:`yield()` 适合"高优先级线程主动谦让、避免饿死低优先级线程"等场景;但它是**建议性**的,生产环境一般不依赖它做精确控制(调度器可能忽略)
+- **isAlive 的典型用途**:`isAlive()` 常用于轮询判断子线程是否执行完(`while (t.isAlive()) { ... }`);但更优雅的做法是用 `join()` 阻塞等待或 `FutureTask.get()` 获取结果(避免空转消耗 CPU)
+- **sleep 与 wait 的区别**:`sleep()` 是 Thread 的静态方法,**不释放锁**(monitor);`wait()` 是 Object 的实例方法,必须持有锁时调用且**会释放锁**(后续线程同步课程会深入)
+- **线程状态(Thread.State 枚举)**:Java 定义 6 种状态——NEW / RUNNABLE(就绪+运行合并)/ BLOCKED(抢锁失败)/ WAITING(无限等)/ TIMED_WAITING(限时等)/ TERMINATED(终点);`sleep()` 期间线程处于 **TIMED_WAITING(计时等待)** 状态;`join()` 让调用线程进入 **WAITING**,`join(timeout)` 进入 **TIMED_WAITING**
+- **getState() 方法**:`Thread.getState()` 返回线程当前状态对应的 `Thread.State` 枚举值,配合演示可以观察线程从 NEW → RUNNABLE → TIMED_WAITING → TERMINATED 的完整生命周期
+- **join 的应用场景**:`join()` 常用于"主线程等待子线程计算结果/执行完成后,再继续处理"的场景;`join(timeout)` 适合"最多等多久"的限时协作(如限时等待子任务完成)
+- **interrupt 的正确用法**:`interrupt()` 是 Java 协作式中断的标准手段——线程内部**主动检查中断标志**(`isInterrupted()` / `Thread.interrupted()`)或在阻塞方法中收到 `InterruptedException` 来响应;`InterruptedException` 抛出时会**清除中断标志**,catch 后重设标志是常见最佳实践
+- **Thread.interrupted() vs isInterrupted()**:静态方法 `Thread.interrupted()` 检查中断标志**并清除**;实例方法 `isInterrupted()` 只检查**不清除**——两者都常用于循环任务的中断响应
+- **FutureTask.get() 与 join 的关联**:第 8 节作业中 `ft.get()` 获取 Callable 结果,本质也是**阻塞等待**线程任务完成——与 `join()` 有相似的"等待"语义(get() 底层也是等待任务结束)
+- **抢占式调度的局限**:设置优先级只是"提高获得 CPU 的概率",**不能保证执行顺序**,也不适合做严格的任务先后控制——生产环境更常用**线程池(ExecutorService)** + 任务队列来精细控制
+- **FutureTask 与 Future 接口**:`FutureTask` 实现了 `Future` 接口——`get()` 获取结果、`isDone()` 判断完成、`cancel()` 取消任务;`get()` 是**阻塞方法**,会一直等待该线程任务执行完才返回
+- **Callable 的现代写法**:生产环境中更常用 `ExecutorService.submit(Callable)` 直接返回 `Future`,比手动 `new Thread + FutureTask` 更规范(线程复用、统一管理)
+- **Lambda 回顾**:Runnable 是**函数式接口**(只有一个抽象方法 run()),所以可以用 Lambda 表达式创建任务对象(回顾 08-04 函数式编程);Demo01 中 `Runnable run = () -> {...}` 就是典型写法
+- **线程安全预告**:多线程并发访问**共享数据**时会引发数据竞争(如两个线程同时给同一个变量自增);后续将学习 `synchronized` 同步、`Lock` 锁、`volatile` 等机制解决线程安全问题
+- **任务与线程解耦**:Runnable / Callable 描述"**任务**"(做什么),Thread / FutureTask 承载"**线程**"(怎么跑)——同一个任务对象可以交给多个线程执行,这种设计更符合面向接口编程思想

+ 512 - 0
课堂练习题/20260810-线程操作与生命周期练习.md

@@ -0,0 +1,512 @@
+# 2026-08-10 线程操作与生命周期练习(课堂练习用)
+
+> **说明**:练习今日新学知识点——**线程操作方法**(`Thread.sleep()` 线程休眠、`getPriority()` / `setPriority()` 线程优先级、`join()` / `join(long)` 等待线程结束、`interrupt()` / `isInterrupted()` 中断标志机制、`yield()` 主动让出 CPU、`isAlive()` 判断存活)、**守护线程**(`setDaemon()` / `isDaemon()`、JVM 退出机制、守护线程特点)、**线程生命周期**(`Thread.State` 枚举 6 种状态:NEW / RUNNABLE / BLOCKED / WAITING / TIMED_WAITING / TERMINATED、`getState()` 观察状态)、**Object 类线程方法**(`wait()` / `notify()` / `notifyAll()` 等待唤醒)。
+
+---
+
+## 练习一:线程生命周期观察 —— getState() 打印 6 种状态
+
+**难度**:⭐⭐⭐
+**知识点**:`Thread.State` 枚举、`getState()`、线程 6 种状态(NEW / RUNNABLE / TIMED_WAITING / TERMINATED)、`join()`、`sleep()`
+
+**场景描述**:课堂学了**线程生命周期**:Java 中线程有 6 种状态(基于 `Thread.State` 枚举)——新建 NEW、可运行 RUNNABLE、阻塞 BLOCKED、等待 WAITING、计时等待 TIMED_WAITING、终止 TERMINATED。本例用 `getState()` 方法在**不同阶段**打印线程状态,直观观察线程从"出生"到"死亡"的状态变化(可观察到的有 NEW / RUNNABLE / TIMED_WAITING / TERMINATED)。
+
+### 案例需求
+
+使用 `getState()` 方法观察一个线程在"创建→启动→休眠→结束"各阶段的状态。
+
+### 实现步骤
+
+1. 创建一个线程对象(任务:循环打印并 sleep 500ms)
+2. `start()` 之前打印状态 → 预期 **NEW**
+3. `start()` 之后立即打印状态 → 预期 **RUNNABLE**(或运行中)
+4. 主线程 sleep 200ms 后打印子线程状态 → 预期 **TIMED_WAITING**(子线程正在 sleep)
+5. `join()` 等待子线程结束后打印状态 → 预期 **TERMINATED**
+
+### 代码框架
+
+```java
+public class LifecycleTest {
+    public static void main(String[] args) throws InterruptedException {
+        // ========== 1、创建线程对象 ==========
+        Thread t = new Thread(() -> {
+            for (int i = 0; i < 5; i++) {
+                try {
+                    Thread.sleep(500);   // sleep 中 → TIMED_WAITING 计时等待
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                System.out.println("子线程工作中---->" + i);
+            }
+        }, "worker");
+
+        // ========== 2、start() 之前:NEW 状态 ==========
+        // TODO 1: System.out.println("start() 之前状态:" + t.getState());   // 预期 NEW
+
+        // ========== 3、start() 之后:RUNNABLE 状态 ==========
+        // TODO 2: t.start();  并打印状态   // 预期 RUNNABLE
+
+        // ========== 4、主线程 sleep,观察子线程 sleep 期间的状态 ==========
+        // TODO 3: Thread.sleep(200); 后打印 t.getState()   // 预期 TIMED_WAITING
+
+        // ========== 5、join 等待子线程结束,打印 TERMINATED ==========
+        // TODO 4: t.join(); 后打印 t.getState()   // 预期 TERMINATED
+    }
+}
+```
+
+**输出示例**:
+```
+start() 之前状态:NEW
+start() 之后状态:RUNNABLE
+子线程工作中---->0
+子线程 sleep 期间状态:TIMED_WAITING
+子线程工作中---->1
+...
+子线程结束后状态:TERMINATED
+```
+
+**选做加分**:
+- 在主线程中创建 3 个线程,分别打印它们的 `getState()`,对比多个线程并发时的状态
+- 在线程 run() 中调用 `wait()`(需 synchronized 同步块),在主线程打印它的状态观察 **WAITING**
+- 用两个线程竞争一个 `synchronized` 锁,观察未抢到锁的线程状态是否为 **BLOCKED**
+
+**思考题**:
+1. 为什么 `getState()` 在 `start()` 之后立即调用,返回的多半是 RUNNABLE 而不是 RUNNING?(提示:Java 把"运行"和"就绪"合并成一个 RUNNABLE 状态,无法精确区分)
+2. `sleep()` 期间线程处于什么状态?(提示:TIMED_WAITING 计时等待,超时自动恢复)
+3. `join()` 让调用线程进入什么状态?(提示:WAITING 无限等待;join(timeout) 进入 TIMED_WAITING)
+4. 线程为什么 `TERMINATED` 之后就"回不去"了?(提示:TERMINATED 是终点站,run() 结束不可逆)
+
+---
+
+## 练习二:线程优先级 + sleep —— 运动员赛跑并发演示
+
+**难度**:⭐⭐⭐
+**知识点**:`getPriority()` / `setPriority()`(默认 5、范围 1~10)、`Thread.MAX_PRIORITY` / `NORM_PRIORITY` / `MIN_PRIORITY`、`Thread.sleep()`、Lambda 创建 Runnable、抢占式调度模型、多线程并发随机性
+
+**场景描述**:课堂学了**线程优先级**:Java 采用**抢占式调度模型**,**优先级高的线程**获得 CPU 时间片更多;线程默认优先级是 5,范围 1~10。本例用 3 个"运动员"线程赛跑,分别设置最高 / 普通 / 最低优先级,配合 `sleep()` 模拟跑步,观察优先级对获得 CPU 机会的影响,以及多线程执行的随机性。
+
+### 案例需求
+
+创建 3 个运动员线程(优先级分别为 10 / 5 / 1),每个跑 10 圈(每圈 sleep 100ms),观察到达终点的先后顺序。
+
+### 实现步骤
+
+1. 用 Lambda 创建 Runnable 任务(获取当前线程名 + 优先级,循环打印圈数)
+2. 创建 3 个 Thread 对象(运动员1 / 2 / 3)
+3. 打印默认优先级(验证都是 5)
+4. 分别设置优先级:`MAX_PRIORITY`(10) / `NORM_PRIORITY`(5) / `MIN_PRIORITY`(1)
+5. 修改后再打印一次优先级,验证 setPriority 生效
+6. `start()` 启动 3 个线程,观察输出交错
+
+### 代码框架
+
+```java
+public class RaceTest {
+    public static void main(String[] args) {
+        // ========== 1、Lambda 创建 Runnable 任务 ==========
+        Runnable runner = () -> {
+            // TODO 1: 获取当前线程名和优先级
+            //         用 Thread.currentThread().getName() / getPriority()
+            for (int i = 1; i <= 10; i++) {
+                try {
+                    Thread.sleep(100);   // 模拟跑步速度
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                // TODO 2: 打印 "线程名(优先级X)跑完第 i 圈"
+            }
+            // TODO 3: 打印 "线程名 到达终点!"
+        };
+
+        // ========== 2、创建 3 个运动员线程 ==========
+        // TODO 4: new Thread(runner, "运动员1") / "运动员2" / "运动员3"
+
+        // ========== 3、打印默认优先级(都是 5)==========
+        // TODO 5: System.out.println(t1.getName() + " 默认优先级:" + t1.getPriority());
+
+        // ========== 4、设置不同优先级 ==========
+        // TODO 6: t1.setPriority(Thread.MAX_PRIORITY);    // 10
+        //         t2.setPriority(Thread.NORM_PRIORITY);   // 5
+        //         t3.setPriority(Thread.MIN_PRIORITY);    // 1
+
+        // ========== 5、再次打印优先级 ==========
+        // TODO 7: 打印三个线程修改后的优先级,验证 10 / 5 / 1
+
+        // ========== 6、启动 3 个线程 ==========
+        // TODO 8: t1.start(); t2.start(); t3.start();
+    }
+}
+```
+
+**输出示例**:
+```
+运动员1 默认优先级:5
+运动员2 默认优先级:5
+运动员3 默认优先级:5
+运动员1 优先级改为:10
+运动员2 优先级改为:5
+运动员3 优先级改为:1
+运动员1(优先级10)跑完第 1 圈
+运动员3(优先级1)跑完第 1 圈
+运动员2(优先级5)跑完第 1 圈
+...
+```
+
+**选做加分**:
+- 把 `setPriority(11)` 传入试试,观察抛出的 `IllegalArgumentException`
+- 去掉 `Thread.sleep(100)` 再运行,观察输出速度与交错变化——sleep 放慢速度便于观察
+- 循环运行多次,统计"到达终点"的先后,观察高优先级线程(10)是否**更可能**先到
+
+**思考题**:
+1. 线程默认优先级是多少?取值范围是多少?(提示:默认 5,范围 1~10)
+2. 高优先级线程一定先执行完吗?(提示:不一定!优先级只是提高获得 CPU 的"概率",抢占式调度 + 随机性决定实际顺序)
+3. 为什么 Java 要用抢占式调度模型?(提示:优先让优先级高的线程使用 CPU,优先级高的线程获得的时间片更多)
+4. `Thread.sleep(100)` 在这个例子中起什么作用?(提示:主动让出时间片、放慢速度,让并发输出交错更明显)
+
+---
+
+## 练习三:守护线程 —— 后台心跳监控(JVM 退出机制验证)
+
+**难度**:⭐⭐⭐⭐
+**知识点**:`setDaemon(boolean)`、`isDaemon()`、用户线程 vs 守护线程、JVM 退出条件(所有用户线程结束才退出)、守护线程特点(start() 前设置、被强杀时 finally 不保证执行)
+
+**场景描述**:课堂学了**守护线程**:它是服务型线程,专门为用户线程提供后台支持;**只要还有一个用户线程存活,JVM 就不会退出**,而**只剩守护线程时 JVM 直接退出**,守护线程被强制终止。本例实现一个"主任务(用户线程)+ 心跳监控(守护线程)"的场景,验证 main 结束后 JVM 是否退出、守护线程是否被强制终止。
+
+### 案例需求
+
+创建主任务线程(用户线程,执行 3 秒)和心跳监控线程(守护线程,无限循环报告心跳)。观察 main 结束后:用户线程还在跑时 JVM 是否退出?守护线程是否还在打印?
+
+### 实现步骤
+
+1. 创建用户线程(主任务):循环 3 次,每次 sleep 1 秒打印"主任务执行中...第 N 秒"
+2. 创建守护线程(心跳监控):无限循环,每 300ms 打印一次心跳次数
+3. 在 `start()` **之前**调用 `heart.setDaemon(true)`
+4. 打印两个线程的 `isDaemon()`,验证守护属性
+5. `start()` 启动两个线程,观察 main 结束后的输出
+
+### 代码框架
+
+```java
+public class DaemonTest {
+    public static void main(String[] args) {
+        // ========== 1、用户线程(主任务):执行 3 秒 ==========
+        Thread user = new Thread(() -> {
+            for (int i = 1; i <= 3; i++) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                // TODO 1: 打印 "主任务执行中...第 i 秒"
+            }
+            // TODO 2: 打印 "主任务执行完毕!"
+        }, "主任务");
+
+        // ========== 2、守护线程(心跳监控):无限循环 ==========
+        Thread heart = new Thread(() -> {
+            int count = 0;
+            while (true) {
+                try {
+                    Thread.sleep(300);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                // TODO 3: 打印 "心跳检测中...第 (++count) 次"
+            }
+        }, "心跳监控");
+
+        // ========== 3、设置守护线程(必须在 start 之前!)==========
+        // TODO 4: heart.setDaemon(true);
+
+        // ========== 4、验证 isDaemon ==========
+        // TODO 5: 打印 heart.isDaemon()(预期 true)和 user.isDaemon()(预期 false)
+
+        // ========== 5、启动两个线程 ==========
+        // TODO 6: user.start(); heart.start();
+        // main 结束后:只剩守护线程 → JVM 退出 → 心跳线程被强制终止
+    }
+}
+```
+
+**输出示例**:
+```
+心跳监控是否是守护线程:true
+主任务是否是守护线程:false
+主任务执行中...第 1 秒
+心跳检测中...第 1 次
+心跳检测中...第 2 次
+心跳检测中...第 3 次
+主任务执行中...第 2 秒
+...
+主任务执行完毕!
+        ← main 结束,JVM 退出,心跳检测不再打印
+```
+
+**选做加分**:
+- 把 `heart.setDaemon(true)` 移到 `heart.start()` **之后**,观察抛 `IllegalThreadStateException`
+- 把心跳线程改成用户线程(不 setDaemon),运行后观察 main 结束后 JVM **是否退出**——心跳会一直打印停不下来
+- 在心跳线程 run() 的 try-finally 中打印"资源清理",观察守护线程被强杀时 finally 是否执行
+
+**思考题**:
+1. JVM 什么时候退出?(提示:所有用户线程(普通线程)都结束时才退出;main 主线程也是用户线程)
+2. 为什么 main 结束了,心跳监控(守护线程)还会被强制终止?(提示:只剩守护线程时 JVM 直接结束,守护线程被强杀)
+3. `setDaemon(true)` 为什么必须在 `start()` 之前调用?(提示:线程启动后守护属性不可修改,否则抛 IllegalThreadStateException)
+4. 守护线程适合做什么任务?能不能用来做资源清理?(提示:适合心跳检测、后台日志、监控统计等;被强杀时 finally 不保证执行,不能用于资源清理)
+
+---
+
+## 练习四:join + interrupt + yield —— 主线程协作等待
+
+**难度**:⭐⭐⭐⭐
+**知识点**:`join()` / `join(long)` 等待线程结束、`interrupt()` / `isInterrupted()` 中断标志机制、`InterruptedException`、catch 后重设中断标志、`yield()` 主动让出 CPU
+
+**场景描述**:课堂学了线程操作方法:`join()` 让当前线程等待目标线程执行完毕;`interrupt()` 设置中断标志(不杀线程),线程在 sleep/wait/join 中被中断会抛 `InterruptedException` 并清除标志,catch 后通常重设标志;`yield()` 主动让出 CPU 时间片。本例综合运用这三种方法:① 主线程 `join()` 等子线程完成;② 中断一个正在 sleep 的线程;③ 子线程用 `yield()` 让出 CPU。
+
+### 案例需求
+
+演示 join / interrupt / yield 三种线程操作方法的协作场景。
+
+### 实现步骤
+
+1. **join 演示**:创建 worker-1 线程(循环 5 次:yield 让出 CPU → 打印 → sleep 200ms),主线程 `worker.join()` 等它结束
+2. **interrupt 演示**:创建 worker-2 线程(sleep 5000ms 模拟长任务),主线程 sleep 500ms 后 `interrupt()` 中断它,观察 catch 中抛异常、重设标志
+3. 两个子线程结束后,主线程打印"主线程结束"
+
+### 代码框架
+
+```java
+public class CooperationTest {
+    public static void main(String[] args) throws InterruptedException {
+        // ========== 1、join + yield 演示 ==========
+        Thread worker = new Thread(() -> {
+            for (int i = 0; i < 5; i++) {
+                // TODO 1: Thread.yield();   主动让出 CPU 时间片
+                // TODO 2: 打印 "worker-1 工作中---> i"
+                try {
+                    Thread.sleep(200);
+                } catch (InterruptedException e) {
+                    // TODO 3: Thread.currentThread().interrupt();   重设中断标志
+                    break;
+                }
+            }
+        }, "worker-1");
+
+        worker.start();
+        // TODO 4: worker.join();   主线程等待 worker-1 结束
+        // TODO 5: 打印 "worker-1 已结束,主线程继续..."
+
+        // ========== 2、interrupt 中断演示 ==========
+        Thread sleeper = new Thread(() -> {
+            try {
+                Thread.sleep(5000);   // 长任务:睡 5 秒
+                System.out.println("任务完成");   // 被中断就不会执行到这
+            } catch (InterruptedException e) {
+                // TODO 6: Thread.currentThread().interrupt();   重设中断标志
+                // TODO 7: 打印 "sleep 中被中断,isInterrupted()= 当前值"
+            }
+        }, "worker-2");
+
+        sleeper.start();
+        Thread.sleep(500);       // 让 sleeper 先进入 sleep 状态
+        // TODO 8: sleeper.interrupt();   向 worker-2 发送中断请求
+        sleeper.join();          // 主线程等 worker-2 处理完中断
+        // TODO 9: 打印 "主线程结束"
+    }
+}
+```
+
+**输出示例**:
+```
+worker-1 工作中---> 0
+worker-1 工作中---> 1
+...
+worker-1 已结束,主线程继续...
+sleep 中被中断,isInterrupted()= true
+主线程结束
+```
+
+**选做加分**:
+- 把 `worker.join()` 改成 `worker.join(500)`,观察主线程最多等 500ms 就继续(不等 worker-1 跑完 5 轮)
+- 去掉 catch 里的 `Thread.currentThread().interrupt()`,观察外层 `isInterrupted()` 的值变化——体会"InterruptedException 抛出时标志被清除"
+- 在线程 run() 中写 `while (true)` + `if (Thread.currentThread().isInterrupted()) break;`,用 `interrupt()` 优雅地结束无限循环线程
+
+**思考题**:
+1. `join()` 和 `join(long)` 的区别是什么?(提示:join() 无限等待目标线程结束;join(millis) 最多等待指定毫秒,超过就不等)
+2. `interrupt()` 会杀死线程吗?(提示:不会!只是设置中断标志,请求线程自行响应,是否退出由线程自己决定)
+3. 为什么 `InterruptedException` 抛出的同时会**清除中断标志**?(提示:阻塞方法被中断时先清标志再抛异常;catch 后重新 interrupt() 是为了让外层 isInterrupted() 能感知到中断)
+4. `yield()` 和 `sleep(ms)` 有什么区别?(提示:yield 只是让出一次 CPU 回到 RUNNABLE,可能马上又被调度;sleep 是限时休眠进入 TIMED_WAITING)
+
+---
+
+# 参考答案要点
+
+## 练习一:LifecycleTest(getState() 观察线程状态)
+
+```java
+// 1、start() 之前:NEW(TODO 1)
+System.out.println("start() 之前状态:" + t.getState());   // NEW
+
+// 2、start() 之后:RUNNABLE(TODO 2)
+t.start();
+System.out.println("start() 之后状态:" + t.getState());   // RUNNABLE
+
+// 3、主线程 sleep 200ms 后:子线程处于 TIMED_WAITING(TODO 3)
+Thread.sleep(200);
+System.out.println("子线程 sleep 期间状态:" + t.getState());   // TIMED_WAITING
+
+// 4、join 等待子线程结束:TERMINATED(TODO 4)
+t.join();
+System.out.println("子线程结束后状态:" + t.getState());   // TERMINATED
+```
+
+## 练习二:RaceTest(线程优先级赛跑)
+
+```java
+// 1、Lambda 创建 Runnable 任务(TODO 1~3)
+Runnable runner = () -> {
+    String name = Thread.currentThread().getName();
+    int priority = Thread.currentThread().getPriority();
+    for (int i = 1; i <= 10; i++) {
+        try {
+            Thread.sleep(100);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        System.out.println(name + "(优先级" + priority + ")跑完第 " + i + " 圈");
+    }
+    System.out.println(name + " 到达终点!");
+};
+
+// 2、创建 3 个运动员线程(TODO 4)
+Thread t1 = new Thread(runner, "运动员1");
+Thread t2 = new Thread(runner, "运动员2");
+Thread t3 = new Thread(runner, "运动员3");
+
+// 3、打印默认优先级(TODO 5)
+System.out.println(t1.getName() + " 默认优先级:" + t1.getPriority());   // 5
+
+// 4、设置不同优先级(TODO 6)
+t1.setPriority(Thread.MAX_PRIORITY);    // 10
+t2.setPriority(Thread.NORM_PRIORITY);   // 5
+t3.setPriority(Thread.MIN_PRIORITY);    // 1
+
+// 5、再次打印(TODO 7)
+System.out.println(t1.getName() + " 优先级改为:" + t1.getPriority());   // 10
+System.out.println(t2.getName() + " 优先级改为:" + t2.getPriority());   // 5
+System.out.println(t3.getName() + " 优先级改为:" + t3.getPriority());   // 1
+
+// 6、启动 3 个线程(TODO 8)
+t1.start();
+t2.start();
+t3.start();
+```
+
+## 练习三:DaemonTest(守护线程心跳监控)
+
+```java
+// 1、用户线程(主任务)(TODO 1~2)
+Thread user = new Thread(() -> {
+    for (int i = 1; i <= 3; i++) {
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        System.out.println("主任务执行中...第 " + i + " 秒");
+    }
+    System.out.println("主任务执行完毕!");
+}, "主任务");
+
+// 2、守护线程(心跳监控)(TODO 3)
+Thread heart = new Thread(() -> {
+    int count = 0;
+    while (true) {
+        try {
+            Thread.sleep(300);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        System.out.println("心跳检测中...第 " + (++count) + " 次");
+    }
+}, "心跳监控");
+
+// 3、设置守护线程(start 之前)(TODO 4)
+heart.setDaemon(true);
+
+// 4、验证 isDaemon(TODO 5)
+System.out.println("心跳监控是否是守护线程:" + heart.isDaemon());   // true
+System.out.println("主任务是否是守护线程:" + user.isDaemon());       // false
+
+// 5、启动(TODO 6)
+user.start();
+heart.start();
+// main 结束 → 只剩守护线程 → JVM 退出 → 心跳被强制终止
+```
+
+## 练习四:CooperationTest(join + interrupt + yield)
+
+```java
+// 1、join + yield 演示(TODO 1~5)
+Thread worker = new Thread(() -> {
+    for (int i = 0; i < 5; i++) {
+        Thread.yield();   // 主动让出 CPU
+        System.out.println("worker-1 工作中---> " + i);
+        try {
+            Thread.sleep(200);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();   // 重设中断标志
+            break;
+        }
+    }
+}, "worker-1");
+
+worker.start();
+worker.join();   // 主线程等待 worker-1 结束
+System.out.println("worker-1 已结束,主线程继续...");
+
+// 2、interrupt 中断演示(TODO 6~9)
+Thread sleeper = new Thread(() -> {
+    try {
+        Thread.sleep(5000);
+        System.out.println("任务完成");
+    } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();   // 重设中断标志
+        System.out.println("sleep 中被中断,isInterrupted()= " + Thread.currentThread().isInterrupted());
+    }
+}, "worker-2");
+
+sleeper.start();
+Thread.sleep(500);      // 让 sleeper 先进入 sleep
+sleeper.interrupt();    // 发送中断请求
+sleeper.join();         // 主线程等 worker-2 处理完中断
+System.out.println("主线程结束");
+```
+
+---
+
+# 涵盖知识点总览
+
+| 知识点 | 说明 |
+|--------|------|
+| `Thread.State` 枚举 | 线程 6 种状态:NEW / RUNNABLE / BLOCKED / WAITING / TIMED_WAITING / TERMINATED |
+| `getState()` | 返回线程当前状态的枚举值,用于观察线程生命周期 |
+| NEW → RUNNABLE | `new Thread()` 后未 `start()` 是 NEW;`start()` 后进入 RUNNABLE(运行或等时间片) |
+| TIMED_WAITING | `sleep(ms)` / `wait(timeout)` / `join(timeout)` 限时等待状态 |
+| TERMINATED | run() 执行完毕或抛未捕获异常结束,终点站不可逆 |
+| `getPriority()` | 返回线程优先级(默认 5) |
+| `setPriority(int)` | 修改线程优先级,范围 1~10(超出抛 IllegalArgumentException) |
+| 优先级常量 | `MAX_PRIORITY`(10) / `NORM_PRIORITY`(5) / `MIN_PRIORITY`(1) |
+| 抢占式调度模型 | 优先让优先级高的线程使用 CPU,同优先级随机选择;Java 默认采用 |
+| 多线程随机性 | 谁抢到 CPU 时间片不确定,执行顺序具有随机性 |
+| `setDaemon(boolean)` | 标记守护线程,必须在 start() 之前调用 |
+| `isDaemon()` | 判断线程是否是守护线程 |
+| JVM 退出机制 | 所有用户线程结束才退出;只剩守护线程时 JVM 直接退出并强杀守护线程 |
+| 守护线程特点 | 服务型线程、子线程默认继承守护属性、被强杀时 finally 不保证执行 |
+| `join()` | 当前线程等待目标线程执行完毕(进入 WAITING) |
+| `join(long)` | 最多等待指定毫秒,超过就不等(进入 TIMED_WAITING) |
+| `interrupt()` | 设置中断标志不杀线程;阻塞中被中断抛 InterruptedException 并清除标志 |
+| `isInterrupted()` | 检查中断标志(不清除);`Thread.interrupted()` 检查并清除 |
+| 重设中断标志 | catch 中 `Thread.currentThread().interrupt()` 恢复标志,方便外层感知 |
+| `yield()` | 主动让出 CPU 时间片重新竞争;只是建议,调度器不一定采纳 |
+| `wait()` / `notify()` / `notifyAll()` | Object 类线程通信方法:等待 / 唤醒(依赖对象锁,进 WAITING) |