目录
一、上节课内容回顾
1. Thread类
2. 线程终止的几种情况
二、本课重点
1. 线程等待:Thread.join
2. 获取当前线程引用
3. 线程休眠
4. 观察线程的所有状态
5. 线程不安全问题
6. 原子性
7. 内存可见性
8. 指令重排序
一、上节课内容回顾
1. Thread类
Thread创建的实例和操作系统中的线程是一一对应的。
创建线程:
继承Thread,重写run
实现Runnable,重写run,搭配Thread
使用匿名内部类,实现Runnable
使用命名内部类,实现Runnable
lambda
线程属性: name、id、前台线程/后台线程
线程的终止/中断:
isInterrupted
interrupt
public 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) { // 在主线程中计算 1+2+...+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 表达式中如果出现 this,this 指向的是外部类的对象
如果使用匿名内部类,this 指向的是外部类的对象
如果使用 lambda,this 指向的是外部类的对象
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.State
public class ThreadState { public static void main(String[] args) { for (Thread.State state : Thread.State.values()) { System.out.println(state); } } }Java 给线程引入六种状态 ~~
NEW:创建了 Thread 对象,但是还没 start
public 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: WAITING
mainThread: WAITING
mainThread: WAITING
mainThread: 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_WAITING
mainThread: TIMED_WAITING
mainThread: TIMED_WAITING
mainThread: 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 把寄存器中的数据 +1
save 把寄存器中的数据写回到内存里
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 提供很多种锁的实现,整体的思路类似~~
"互斥""独占"
锁机制,本质上是操作系统提供的功能