珠海公司网站域名注册,品牌seo培训咨询,宝塔 wordpress 教程,做爰片免费网站给我看看✨个人主页#xff1a;bit me#x1f447; ✨当前专栏#xff1a;Java EE初阶#x1f447; ✨每日一语#xff1a;低头赶路#xff0c;敬事如仪#xff1b;自知自心#xff0c;其路则明。 目 录#x1f378;一. 线程不安全#x1f379;二. 线程不安全的原因#x1f… ✨个人主页bit me ✨当前专栏Java EE初阶 ✨每日一语低头赶路敬事如仪自知自心其路则明。 目 录一. 线程不安全二. 线程不安全的原因一. 线程不安全
多线程编程最重要也是最困难的问题就是线程安全问题它的万恶之源罪魁祸首就是调度器的随机调度 / 抢占式执行 这个过程
线程不安全在随机调度之下程序执行有多种可能其中的某些可能导致代码出现了 bug 线程不安全 / 线程安全问题
例如两个线程对一个变量进行并发的自增创建俩线程让这俩线程同时并发的对一个变量自增 5w 次最终预期能一共自增 10w 次
//创建俩线程让这俩线程同时并发的对一个变量自增 5w 次最终预期能一共自增 10w 次
class Counter{//用来保存计数的变量public int count;public void increase(){count;}
}
public class Demo14 {//这个实例用来进行累加public static Counter counter new Counter();public static void main(String[] args) {Thread t1 new Thread(()-{for (int i 0; i 50000; i) {counter.increase();}});Thread t2 new Thread(()-{for (int i 0; i 50000; i) {counter.increase();}});t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(count: counter.count);}
}运行结果 再运行一次 发现随机调度顺序不一样结果也就不一样
那上面的 bug 如何出现的 执行一段代码需要让 CPU 把对应的指令从内存中读取出来然后再执行 像 count 一行代码对应三个机器指令 1.从内存读取数据到 CPUload2.在 CPU 寄存器中完成加法运算add3.把寄存器的数据写回到内存中sava 指令的排序方式 上述两种指令的排序方式恰好能到 2 。但是还有许许多多的排列组合方式就都不一定了。 此时总和就是 1 了 这些还有许许多多就不在此举例了
根据上述的总结俩种极端情况就是 5w 和 10w 。然后其他的情况就是 5w 和 10w 之间了。
拓展 操作系统中的随机调度严格意义上来说其实不是 “随机调度” 。在内部他有自己的一套调度过程我们不需要理解这个过程了解了也无法改变这个调度。 二. 线程不安全的原因
1. 操作系统的随机调度 / 抢占式执行。【罪魁祸首万恶之源】 2. 多个线程修改同一个变量。【三个条件缺一不可别的情况都没事儿】所以写代码中可以针对这三个点进行改变进行规避但是范围有限不是所有的场景都可以规避掉 3. 有些修改操作不是原子的【不可拆分的最小单位就叫原子】 通过 来修改 只对应一条机器指令视为是原子的 通过 来修改 对应三条机器指令则不是原子的 什么是原子性? 我们把一段代码想象成一个房间每个线程就是要进入这个房间的人。如果没有任何机制保证A进入房间之后还没有出来B 是不是也可以进入房间打断 A 在房间里的隐私。这个就是不具备原子性的。 那我们应该如何解决这个问题呢是不是只要给房间加一把锁A 进去就把门锁上其他人是不是就进不来了。这样就保证了这段代码的原子性了。(后续详解关于锁) 后果如果一个线程正在对一个变量操作中途其他线程插入进来了如果这个操作被打断了结果就可能是错误的。 4. 内存可见性引起的线程安全问题。【内存改了但是在优化的背景下读不到看不见】 例如一个线程读一个线程修改线程 1 LOAD 之后需要进行 TEST 然后继续 LOAD 再继续 TEST 这样循环走下去但是在程序运行过程中可能会涉及到一个操作 “优化” (可能是编译器 Javac也可能是 JVM Java也可能是操作系统)由于 LOAD 读的操作太慢反复读每次读到的数据都是一样的JVM 就对此做出了优化不需要重复在内存中读取了直接就重复用第一次从内存读到寄存器的数据就好了此时在优化之后线程 2 突然写了一个数据由于线程 1 已经优化成读寄存器了因此线程 2 的修改线程 1 感知不到。 上述优化在单线程环境下没问题但是多线程情况下就可能产生问题多线程环境太复杂编译器/JVM/操作系统进行优化的时候就可能产生误判针对这个问题Java 引入了 volatile 关键字让程序猿手动禁止编译器针对某个变量进行上述优化 5. 指令重排序。【调整代码执行顺序】 也是编译器 / JVM / 操作系统优化 优化在单线程环境下没问题但是多线程情况下就可能产生问题例如 Test t new Test(); 在底层就有三个步骤 创建内存空间往这个内存空间上构造一个对象把这个内存的引用赋值给 t 此处就容易出现指令重排序引入的问题2 和 3 的顺序是可以调换的在单线程下调换这俩是没影响的多线程下就不行了。例如我们俩线程第一个线程按照先 2 后 3 的顺序另一个线程尝试读取 t 的引用当第二个线程读到 t 为非 null 的时候此时 t 就一定是一个有效对象如果是按照先 3 后 2 的顺序第二个线程读到 t 为非 null 的时候仍然可能是一个无效对象