目录一、上节课内容回顾1. Thread类2. 线程终止的几种情况二、本课重点1. 线程等待Thread.join2. 获取当前线程引用3. 线程休眠4. 观察线程的所有状态5. 线程不安全问题6. 原子性7. 内存可见性8. 指令重排序一、上节课内容回顾1. Thread类Thread创建的实例和操作系统中的线程是一一对应的。创建线程继承Thread重写run实现Runnable重写run搭配Thread使用匿名内部类实现Runnable使用命名内部类实现Runnablelambda线程属性​ name、id、前台线程/后台线程线程的终止/中断isInterruptedinterruptpublic class Demo { public static void main(String[] args) { Thread t new Thread(() - { while (!Thread.currentThread().isInterrupted()) { System.out.println(hello thread); } }); t.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } t.interrupt(); } }实际开发中catch中一般不会再次抛出异常重试记录日志忽略2. 线程终止的几种情况立即退出interrupt()停止sleep被唤醒后发现中断标记主动退出不退出没有break就是不退main线程调用t.interrupt()t线程的while循环检测到中断标记主动退出。如果catch中没有把异常抛出而是默默处理记录日志或忽略线程会继续执行。二、本课重点1. 线程等待Thread.join线程之间是随机调度执行的~~干扰两个线程的结束顺序后结束的线程等待先结束的线程执行完~~public class Demo10 { private static int result 0; // 预期结果应该是 1000 public static void main(String[] args) { // 在主线程中计算 12...1000 // 创建新线程也执行相同的计算逻辑 Thread t new Thread(() - { for (int i 1; i 1000; i) { result i; } System.out.println(线程计算完成); }); t.start(); Thread.sleep(1000); System.out.println(result); } }执行顺序随机了~~如果t线程的逻辑更复杂如何评估计算时间呢让main线程等待t线程执行完毕~~public class Demo11 { private static int result 0; // 预期结果应该是 1000 public static void main(String[] args) { // 获取当前线程的引用 Thread mainThread Thread.currentThread(); Thread t new Thread(() - { for (int i 1; i 1000; i) { result i; } System.out.println(线程计算完成); try { mainThread.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(result); }); t.start(); // 进行计算 for (int i 1; i 1000; i) { result i; } } }如果等的线程一直不退出呢死等~~带有超时时间的等待~~public class Demo12 { public static void main(String[] args) throws InterruptedException { Thread t new Thread(() - { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }); t.start(); t.join(1000); // 最多等 1000ms System.out.println(t.getState()); } }join 等到超时时间结束~~只是从阻塞状态还原成就绪的状态~~继续往下执行还需要等待操作系统的调度如果调用 join 之前t 已经执行完了再次调用 join此时不会阻塞~~2. 获取当前线程引用这个方法我们已经非常熟悉了public class Demo9 { public static void main(String[] args) { Thread t new Thread(() - { Thread cur Thread.currentThread(); while (!cur.isInterrupted()) { System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { // throw new RuntimeException(e); // e.printStackTrace(); // 执行一些其他逻辑~~ // 退出之后做一些释放资源类工作 // 没有 break 就是不退出~~ break; } } }); t.start(); } }注意当 lambda 表达式中如果出现 thisthis 指向的是外部类的对象如果使用匿名内部类this 指向的是外部类的对象如果使用 lambdathis 指向的是外部类的对象Thread t new Thread(new Runnable() { Override public void run() { // 此处的 run 是 Runnable 的方法this 指向 Runnable // Runnable 中没有 getName 这样的系列方法 System.out.println(this.getName()); } }); Thread t new Thread(new Runnable() { Override public void run() { // 此处的 run 是 Runnable 的方法this 指向 Runnable //System.out.println(this.getName()); Thread cur Thread.currentThread(); System.out.println(cur.getName()); } }); Thread t2 new Thread(() - { Thread cur Thread.currentThread(); System.out.println(cur.getName()); }); Thread t2 new Thread(() - { System.out.println(this.getName()); // 报错 });3. 线程休眠休眠的本质是放弃 CPU 的使用权把 CPU 让给其他线程执行~~public class Demo14 { public static void main(String[] args) throws InterruptedException { Thread t new Thread(() - { for (int i 1; i 1000; i) { System.out.println(i); try { Thread.sleep(1000); // 休眠 1s } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); } }4. 观察线程的所有状态线程的状态是一个枚举类型 Thread.Statepublic class ThreadState { public static void main(String[] args) { for (Thread.State state : Thread.State.values()) { System.out.println(state); } } }Java 给线程引入六种状态 ~~NEW创建了 Thread 对象但是还没 startpublic class Demo13 { public static void main(String[] args) { Thread t new Thread(() - { while (true) { System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); System.out.println(t.getState()); t.start(); t.join(); System.out.println(t.getState()); } }RUNNABLE一个线程正在 CPU 上执行WAITING这几个都表示线程等着其他事情TIMED_WAITING这几个都表示线程等着其他事情BLOCKED这几个都表示线程等着其他事情public class Demo13 { public static void main(String[] args) throws InterruptedException { Thread t new Thread(() - { while (true) { System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); System.out.println(t.getState()); t.start(); Thread.sleep(10); System.out.println(t.getState()); t.join(); System.out.println(t.getState()); } }mainThread: WAITINGmainThread: WAITINGmainThread: WAITINGmainThread: WAITING......TIMED_WAITING这几个都表示线程等着其他事情public class Demo13 { public static void main(String[] args) throws InterruptedException { Thread t new Thread(() - { while (true) { System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); System.out.println(t.getState()); t.start(); t.join(10000); System.out.println(t.getState()); } }mainThread: TIMED_WAITINGmainThread: TIMED_WAITINGmainThread: TIMED_WAITINGmainThread: TIMED_WAITING......BLOCKED这几个都表示线程等着其他事情特指由于锁引起的阻塞public class Demo13 { public static void main(String[] args) throws InterruptedException { Thread mainThread Thread.currentThread(); Thread t new Thread(() - { while (true) { System.out.println(mainThread: mainThread.getState()); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); System.out.println(t.getState()); t.start(); t.join(); System.out.println(t.getState()); } }5. 线程不安全问题count;这个代码对应了三个 CPU 的指令~~load 把内存中的数据加载到寄存器中add 把寄存器中的数据 1save 把寄存器中的数据写回到内存里public class Demo14 { private static int count 0; // 3 usages public static void main(String[] args) { // 创建两个线程分别对一个变量进行 5w 次的自增操作 Thread t1 new Thread(() - { for (int i 0; i 50000; i) { count; } }); Thread t2 new Thread(() - { for (int i 0; i 50000; i) { count; } }); t1.start(); t2.start(); // 让主线程等待等待上述的两个线程结束 t1.join(); t2.join(); System.out.println(count count); } }预期的结果10w实际的结果小于 10w有 bug调度顺序是不确定的~~就这两种顺序是正常的两个线程各自循环 5w 次自增5w 对操作中有多少对是有问题的多少对是没问题的~~~上述结果一定是一个 10w 的值~~~是否可能会产生 5w 的值呢 有可能多少种情况无数种操作系统对线程的调度是随机的~~线程安全问题的原因[根本原因] 操作系统对于线程的调度是随机的~~没有办法去应对两个线程针对同一个变量进行修改操作一个线程针对一个变量进行修改 没问题两个线程针对不同变量进行修改 没问题两个线程针对同一个变量进行读取 没问题不使用多线程 单线程 没法充分利用多核 CPU 资源~不使用多线程每个线程搞一个变量比较吃的需求和逻辑的相对常见的方案~~不是 java 中常见6. 原子性修改操作不是原子的~~count 这样的操作是分成了三个 cpu 指令~~指令是 cpu 上执行的基本单位~~锁事务的背后也是和锁密切相关的7. 内存可见性8. 指令重排序String 属于不可变对象~~String 不可变对象是怎么实现很多同学的理解是错的和 final 无关禁止你扩展(继承)没有提供 public 的 set 系列方法~~为啥要这么设计字符串常量池~~计算 hash线程安全~~Java 提供很多种锁的实现整体的思路类似~~互斥独占锁机制本质上是操作系统提供的功能