Day281&282&283.Java内存模型 -Juc
Posted 阿昌喜欢吃黄桃
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day281&282&283.Java内存模型 -Juc相关的知识,希望对你有一定的参考价值。
Java内存模型
一、Java代码到CPU指令
什么是底层原理?
Java代码层—>Class层—>CPU指令
平台指的是:操作系统
编译执行流程:↓↓↓
- JVM实现会带来不同的
翻译
,不同的CPU平台
的机器指令
又有千差万别,无法
保证并发安全的效果一致性
二、JVM内存结构 & Java内存模型 & Java对象模型
1、整体方向:
-
JVM内存结构 ,和Java虚拟机的
运行时区域
有关 -
Java内存模型 ,和Java的
并发
编程有关 -
Java对象模型 ,和Java对象在
虚拟机中的表现形式
有关
2、JVM内存结构
- 堆Heap
- 整个内存占用最大的,内存占用最多的
- 存放对象的实例对象
- 运行时动态分配
- 虚拟机栈(VM stack)Java栈
- 保存基本数据类型
- 保存了对象的引用
- 编译时就确定了大小,在运行时这个大小不会改变
- 方法区(Method Area)
- 存储已加载的static静态变量
- 类信息
- 常量信息
- 包含永久引用—>如新建一个由static修饰的Student类
- 本地方法栈
- 包括了native方法
- 程序计数器
- 占内存区域最小
- 保存当前线程执行到的字节码的行号数,上下文切换的时候,也会被保存
- 包括下次执行 指令、分支、循环等异常处理
3、Java对象模型
对象自身的存储模型
因为Java是面向对象的,所以每一个对象的存储都有一定的存储结构
- 首先在方法区创建出类的信息,instanceKlass
- 每个对象的实例都会放到堆中
- 若对象被调用了,那就会在栈中保存这个对象的引用
三、Java内存模型,JMM
1、JMM是什么?
JMM: Java Memory Model
JMM是一种规范!!!
是一组规范
,需要各个JVM的实现来遵守JMM规范,以便于开发者可以利用这些规范
,更方便地开发多线程程序
2、为什么需要JMM?
不存在JMM:
-
C语言不存在内存模型的概念
-
依赖处理器本身的内存一致性模型,不同处理器的运行结果不一样;
-
无法保证并发安全
因此需要一个标准,让多线程运行的结果可预期
3、JVM是工具类和关键字的原理
四、重排序
- 重排序的例子演示
1、 第一种情况
/******
@author 阿昌
@create 2021-05-28 22:23
*******
* 演示重排序的现象
* 重排序不是100%发生,所以需要多次重复,直到达到某个条件才停止
*/
public class OutOfOrderExecution {
private static int x,y=0;
private static int a,b=0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = a;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("x:"+x);
System.out.println("y:"+y);
}
}
线程thread1和线程thread2,他们的运行顺序会影响到最后的x、y的值;
2、第二种情况
/******
@author 阿昌
@create 2021-05-28 22:23
*******
* 演示重排序的现象
* 重排序不是100%发生,所以需要多次重复,直到达到某个条件才停止
*/
public class OutOfOrderExecution {
private static int x,y=0;
private static int a,b=0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = a;
}
});
thread2.start();
thread1.start();
thread1.join();
thread2.join();
System.out.println("x:"+x);
System.out.println("y:"+y);
}
}
3、第三种情况:
使用工具类CountDownLatch();
countDown()放开闸门
await()设置闸门
/******
@author 阿昌
@create 2021-05-28 22:23
*******
* 演示重排序的现象
* 重排序不是100%发生,所以需要多次重复,直到达到某个条件才停止
*/
public class OutOfOrderExecution {
private static int x,y=0;
private static int a,b=0;
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//加上栅栏
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 1;
x = b;
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
b = 1;
y = a;
}
});
thread2.start();
thread1.start();
//放开闸门
latch.countDown();
thread1.join();
thread2.join();
System.out.println("x:"+x);
System.out.println("y:"+y);
}
}
阿昌这里多次执行了10多次才出现我们需要的效果:↓↓↓,两个方法交替执行
因为上面↑,执行了多次,这里我们对程序进行优化,让他直接执行到y=1,x=1的情况再退出
/******
@author 阿昌
@create 2021-05-28 22:23
*******
* 演示重排序的现象
* 重排序不是100%发生,所以需要多次重复,直到达到某个条件才停止
*/
public class OutOfOrderExecution {
private static int x, y = 0;
private static int a, b = 0;
public static void main(String[] args) throws InterruptedException {
int count = 0;//计数
CountDownLatch latch = new CountDownLatch(1);
for (; ; ) {
count++;
//数据重置
x = 0;
y = 0;
a = 0;
b = 0;
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//加上栅栏
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 1;
x = b;
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
b = 1;
y = a;
}
});
thread2.start();
thread1.start();
//放开闸门
latch.countDown();
thread1.join();
thread2.join();
String result = "第"+count+"次 "+ "(x:"+x+", y:"+y+")";
//死循环结束条件
if (x==1&&y==1){
System.out.println(result);
break;
}else {
System.out.println(result);
}
}
}
}
4、重排序分析
上面的条件成立在 :↓
a=1;
x=b;
这代码的顺序不会改变的情况下才出现的情况,只存在线程之间的交替改变,而不存在线程内部的代码可能会改变的情况
接下来,我们想要让他出现x=0,y=0
的情况
//修改代码部分
if (x == 0 && y == 0) {
System.out.println(result);
break;
} else {
System.out.println(result);
}
他的极有可能发生了重排序的情况:
也就是说,出现了如下情况
y=a;
a=1;
x=b;
b=1;
他先的y赋值,再对a进行赋值,出现了代码内部的顺序颠倒问题
!!
那么!!!什么是重排序
人话版: 程序执行的流程跟代码书写的流程出现了不一致的情况
5、重排序的好处: 提高处理速度
-
对比重排序前后的
指令优化
-
没有发生重排序指令的情况:↓
- 进过重排序后的指令的优化情况:↓↓↓
减少了对a的读取,对a的写入指令次数
6、重排序的3种情况
五、可见性
1、代码演示
/******
@author 阿昌
@create 2021-05-29 21:24
*******
* 演示可见性带来的问题
*/
public class FielidVisibility {
int a = 1;
int b = 2;
public static void main(String[] args) {
while (true){
FielidVisibility test = new FielidVisibility();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.change();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.print();
}
}).start();
}
}
private void print() {
System.out.println("b:"+b+",a:"+a);
}
private void change() {
a=3;
b=a;
}
}
第四种情况是 b=3,a=1的情况,就是因为可见性发生的;
因为当线程可以读到b=3后,如果在主存中读不到线程a=3这个赋值操作,那么就会去读原来的初始化值
那么如何解决这个线程可见性出现的问题???
2、解决方案
使用 volatile
修饰变量,强制每次线程读取的时候都是被修改后的值
/******
@author 阿昌
@create 2021-05-29 21:24
*******
* 解决可见性问题方案:使用 volatile
*/
public class FielidVisibility {
volatile int a = 1;
volatile int b = 2;
public static void main(String[] args) {
while (true){
FielidVisibility test = new FielidVisibility();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.change();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.print();
}
}).start();
}
}
private void print() {
System.out.println("b:"+b+",a:"+a);
}
private void change() {
a=3;
b=a;
}
}
那为什么使用了 volatile 就可以解决可见性问题呢?
3、为什么会有可见性问题
越上面的缓存容量越小,但是执行速度越快;反之越下面的缓存容量越大,但执行速度越慢
另外,L1cache缓存L2cache的一部分数据,当core4核心去修改L1cache的值后,可能core1核心的L1cache的值是从L2cache里面取,所以就会导致各核心的值会出现 不一致性
因此,最主要的原因是:
由于CPU有多层缓存
,导致读数据过期问题
4、Java通过JMM来解决可见性问题
1)什么是主内存&本地内存
线程工作在WorkingMemory
中,他不与主内存直接沟通,后事通过Buffer缓冲区
他们的交互只能通过主内存,不能直接互通;
↓↓下图各个线程通过操作指令区和主内存交互,这样子就可以是的多个线程都能互通
2)主内存和本地内存的关系
5、能保证 可见性 的措施
六、happens-before原则
1、什么是happens-before
也就是说,如果第一份代码运行了,那么第二份代码就一定能看到第一份代码运行的行为
2、什么不是happens-before
3、happens-before原则的内容
他可以帮助自己可见性,也可以帮助其他周围的变量等也具有可见性
- 单线程原则
若是单线程执行,那么后面的语句肯定能看到前面的语句做了的行为结果,保证的情况是 没有发生重排序问题
- 锁操作(synchronized 和 Lock)
- volatile变量
- 线程启动
- 线程join
让下面的代码等待one、two两个线程执行完,再执行
- 传递性:
如果hn(A,B)而且hb(B,C),那么可以退出hb(A,C)
也就是第一行运行,第二行能看到第一行,那第二行运行,第三行能看到第二行;
那么第三行就能看到第一行运行
- 中断:
一个线程被其他线程interrupt时,那么检测中断(isInterrupted)或者抛出InterruptedException一定能看到。
就是说,A被中断了,那么B线程就能检测可见性看到A被中断了。
- 构造方法:
对象构造方法的最右一行指令 happens-before于finalize()放啊的第一行指令
- 工具类的Happens-Before原则
七、volatile关键字
1、什么是volatile
-
volatile是一种
同步机制
,类似与Lock和Synchronized相关,但是他更轻量级
,因为使用volatile并不会
发生上下文切换等
开销很大的行为 -
如果一个变量被volatile修饰,那么JVM会认为这个变量可能会被
并发修改
-
因为开销小,所有能力小
;他做不到像synchronized那样原子保护
,使用的场景比较有限
2、不适用场景
- a++ 场景
/******
@author 阿昌
@create 2021-05-30 17:27
*******
* 不适用volatile场景
*/
public class NoVolatile implements Runnable {
volatile int a;
AtomicInteger realA = new AtomicInteger以上是关于Day281&282&283.Java内存模型 -Juc的主要内容,如果未能解决你的问题,请参考以下文章
(字典树3道水题)codeforces 665E&282E&514C
#282(div2) B. Modular Equations
《安富莱嵌入式周报》第282期:CMSIS-DSP手册引入计算图,树莓派单片机RP2040超频到1GHz,COBS字节编码算法,纯手工为PS1打造全新亚克力外壳
《安富莱嵌入式周报》第282期:CMSIS-DSP手册引入计算图,树莓派单片机RP2040超频到1GHz,COBS字节编码算法,纯手工为PS1打造全新亚克力外壳