1. 到底什么叫"底层原理 "?本章研究的内容是什么?
1.1 重要性
Java 面试的必考知识点。只有学会了这一章的内容,才能说你真正懂了并发。
1.2 从JAVA代码到CPU指令
1.最开始,我们编写的Java 代码,是*.java文件
2.在编译( javac命令)后,从刚才的*.java文件会变出一个新的Java字节码文件( *.class )
3.JVM会执行刚才生成的字节码文件( *.class ) , 并把字节码文件转化为机器指令
4.机器指令可以直接在CPU上运行 ,也就是最终的程序执行
1.3 JVM实现会带来不同的“翻译 ”, 不同的CPU平台 的机器指 令又千差万别,无法 保证并发安全的效果一致
1.4 重点开始向下转移
转化过程的规范、原则
2. 三兄弟 :JVM内存结构 VS Java内存模型 VS Java对象模型
2.1 整体方向
2.1.1 JVM内存结构:和JAVA虚拟机的运行时区域 有关
堆(Heap):通过new 或者其它方式创建的实例对象(包括数组)。如果这些对象不再被引用会被垃圾回收。堆的优势就是在运行时动态分配。
Java栈(VM stack):又称虚拟机栈,保存了各个基本数据类型,以及对于对象的引用。Java堆在编译时会确定大小,在运行时大小不会改变。
方法区(method):存储的是已经加载的各个static静态变量、类信息已经常量信息,还包含永久引用。
本地方法栈:保存的是和本地方法相关的信息;本地方法主要指native 方法。
程序计数器:占用的区域是最小的,保存的是当前线程执行字节码的行号数,在上下文切换时,这个数据会被保存下来,包括需要下一条执行的指令、循环等异常处理。
2.1.2 Java内存模型,和Java的并发 编程有关
2.1.3 Java对象模型,和Java对象在虚拟机中的表现形式 有关,是对对象的抽象。
Java对象 自身的存储模型
JVM会给这 个类创建-个instanceKlass ,保存在方法区 ,用来在JVM层表示该Java类。
当我们在Java代码中,使用new创建一个对象的时候 , JVM会创建一个instanceOopDesc对象,这个对象中包含了对象头 以及实例数据 。
3. JVM是什么
Java Memory Model(JVM内存模型)
-
C 语言不存在内存模型的概念。 -
依赖 处理器,不同的处理器结果不一样 -
无法保证 并发安全 -
需要一个标准 ,让多线程的运行结果可预期 -
是规范
3.1 为什么需要 JMM
需要各个JVM的实现来遵守JMM规范 ,以便开发者可以利用这些规范,更方便地开发多线程程序 。
如果没有这样的一个JMM内存模型来规范,那么很可能经过不同JVM的不同规则的重排序之后,导致不同的虚拟机上的运行结果不一样 ,那是很大的问题。
volatile、synchronized、Lock等的原理都是JMM
如果没有JMM ,那么就需要我们自己指定什么时候用内存栅栏等,那可是相当麻烦的,幸好有了JMM,让我们只需要用同步工具和关键字 就可以开发并发程序。
4. 重排序
4.1 重排序的代码案例 、什么是重排序
public class OutOfOrderExecution {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
a = 1;
x = b;
});
Thread thread2 = new Thread(()->{
b = 1;
y = a;
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println( "x = " + x +",y = " + y);
}
}
程序输出结果
x = 1,y = 0
Process finished with exit code 0
程序结果分析
赋值操作的4行代码的执行顺序决定了最终x和y的结果,一共有3总情况:
thread1 先运行,a=1,x=b(0);b=1,y=a(1); ,最终结果是x=0,y=1 ;thread2 先运行,b=1,y=a(0);a=1,x=b(1); ,最终结果是x=1,y=0 ;b=1;a=1,x=b(1);x=a(1),y=b(1); ,最终结果是x=1,y=1 ;
public class OutOfOrderExecution {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int count =0;
for (;;){
count++;
a = 0;
b = 0;
x = 0;
y = 0;
CountDownLatch latch = new CountDownLatch(1);
Thread thread1 = new Thread(()->{
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 1;
x = b;
});
Thread thread2 = new Thread(()->{
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("当前运行" + count + "次," + "x = " + x + ",y = " +y);
if(x == 1 && y ==1){
break;
}
}
}
}
虽然代码执行顺序可能有很多种情况,但在线程1内部,也就是
a = 1;
x = b;
按照刚才的运行结果这两行代码的执行顺序是不会改变的, 也就是a=1会在x=b前执行,同理,现在2的b=1;会在y=a;之前执行。因此无论如何也不会出现x=;y=0;的情况,但是真实情况会如此吗?
public class OutOfOrderExecution {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int count =0;
for (;;){
count++;
a = 0;
b = 0;
x = 0;
y = 0;
CountDownLatch latch = new CountDownLatch(1);
Thread thread1 = new Thread(()->{
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 1;
x = b;
});
Thread thread2 = new Thread(()->{
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("当前运行" + count + "次," + "x = " + x + ",y = " +y);
if(x == 0 && y ==0){
break;
}
}
}
}
程序输出结果
...
当前运行954次,x = 1,y = 0
当前运行955次,x = 1,y = 0
当前运行956次,x = 0,y = 0
Process finished with exit code 0
程序结果分析
会出现x=0,y=0?那是因为发生了重排序 ,4行代码的执行顺序的其中一种可能是:
y=a;
a=1;
x=b;
b=1;
什么是重排序?在线程1内部的两行代码的实际执行顺序 和代码在Java文件种的顺序 不一致,代码指令不是严格按照语句顺序执行的,它们的顺序改变了,这就是重排序,这里被颠倒的是y=1;和b=1;这两行语句。
4.2 重排序的好处 :提高处理速度
4.2 重排序的3 种情况:编译器优化、CPU指令重排序、内存的"重排序"
5. 可见性
6. 原子性
7. 常见模式问题总结
|