做网站的域名和空间是什么意思,全球网站排名前十,免费dedecms企业网站模板,wordpress 判断首页JMM内存模型 1. 原子性1-1. 问题分析1-2. 问题解决 2. 可见性2-1. 问题分析2-2. 问题解决 3. 有序性3-1. 问题分析3-2. 问题解决 4. CAS与原子性5. synchronized 优化 1. 原子性
很多人将【java 内存结构】与【java 内存模型】傻傻分不清#xff0c;【java 内存模型】是 Java… JMM内存模型 1. 原子性1-1. 问题分析1-2. 问题解决 2. 可见性2-1. 问题分析2-2. 问题解决 3. 有序性3-1. 问题分析3-2. 问题解决 4. CAS与原子性5. synchronized 优化 1. 原子性
很多人将【java 内存结构】与【java 内存模型】傻傻分不清【java 内存模型】是 Java Memory ModelJMM的意思。简单的说JMM 定义了一套在多线程读写共享数据时成员变量、数组时对数据的可见性、有序性、和原子性的规则和保障
1-1. 问题分析
两个线程对初始值为 0 的静态变量一个做自增一个做自减各做 5000 次结果是 0 吗
结果可能是正数、负数、零。为什么呢因为 Java 中对静态变量的自增自减并不是原子操作。
例如对于 i 而言i 为静态变量实际会产生如下的 JVM 字节码指令
getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
iadd // 加法
putstatic i // 将修改后的值存入静态变量i而对应 i-- 也是类似
getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
isub // 减法
putstatic i // 将修改后的值存入静态变量i而 Java 的内存模型如下完成静态变量的自增自减需要在主存和线程内存中进行数据交换 1-2. 问题解决
synchronized同步关键字
synchronized( 对象 ) {要作为原子操作代码
}2. 可见性
2-1. 问题分析
先来看一个现象main 线程对 run 变量的修改对于 t 线程不可见导致了 t 线程无法停止
static boolean run true;
public static void main(String[] args) throws InterruptedException {Thread t new Thread(()-{while(run){// ....}});t.start();Thread.sleep(1000);run false; // 线程t不会如预想的停下来
}为什么会这样
初始状态 t 线程刚开始从主内存读取了 run 的值到工作内存。 2. 因为 t 线程要频繁从主内存中读取 run 的值JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中减少对主存中 run 的访问提高效率 3. 1 秒之后main 线程修改了 run 的值并同步至主存而 t 是从自己工作内存中的高速缓存中读取这个变量的值结果永远是旧值 2-2. 问题解决
volatile易变关键字
它可以用来修饰成员变量和静态成员变量他可以避免线程从自己的工作缓存中查找变量的值必须到主存中获取它的值线程操作 volatile 变量都是直接操作主存保证了共享变量的可见性但不能保证原子性
public class Demo1 {volatile static boolean run true;public static void main(String[] args) throws InterruptedException {Thread t new Thread(() - {while (run) {
// ....}});t.start();Thread.sleep(1000);run false; // 线程t不会如预想的停下来}} 注意 synchronized 语句块既可以保证代码块的原子性也同时保证代码块内变量的可见性。但 缺点是synchronized是属于重量级操作性能相对更低 如果在前面示例的死循环中加入 System.out.println() 会发现即使不加 volatile 修饰符线程 t 也 能正确看到对 run 变量的修改了想一想为什么 进入println源码
public void println(int x) {synchronized (this) {print(x);newLine();}
}可以看出加了synchronized保证了每次run变量都会从主存中获取
3. 有序性
3-1. 问题分析
看下面一个栗子
int num 0;
boolean ready false;
// 线程1 执行此方法
public void actor1(I_Result r) {if(ready) {r.r1 num num;} else {r.r1 1;}
}
// 线程2 执行此方法
public void actor2(I_Result r) {num 2;ready true;
}看到这里可能聪明的小伙伴会想到有下面三种情况
情况1线程1 先执行这时 ready false所以进入 else 分支结果为 1
情况2线程2 先执行 num 2但没来得及执行 ready true线程1 执行还是进入 else 分支结果为1
情况3线程2 执行到 ready true线程1 执行这回进入 if 分支结果为 4因为 num 已经执行过了
但其实还有可能为0哦
有可能还是线程 2 执行 readytrue 切换到线程1 进入if分支相加为0在切回线程 2 执行 num2
这种现象就是指令重排
3-2. 问题解决
volatile 修饰的变量可以禁用指令重排
int num 0;
volatile boolean ready false;
// 线程1 执行此方法
public void actor1(I_Result r) {if(ready) {r.r1 num num;} else {r.r1 1;}
}
// 线程2 执行此方法
public void actor2(I_Result r) {num 2;ready true;
}4. CAS与原子性
5. synchronized 优化