首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Java学习内存模型以及线程安全的可见性问题(八)

上次线程池已经说过了,从今天开始一起了解下JVM内存模型详解。

(一)容易误解的部分

老铁很容易把JAVA的内存区域、JAVA的内存模型,GC分代回收的老年代和新生代也容易搞混,绕进去绕不出来。学习多线程之前一定要搞明白这些问题,可能在你的内心一直认为多线程就是一个工具,所有的底层都是C++来写的,没办法去看,为什么要有java,java其实就是屏蔽了底层的复杂性。

GC内存区域

堆的概念,老年代,新生代,Eden,S0,S1

JAVA的内存区域

JVM运行时的区域:java编译生成class,线程共享部分(方法区,堆内存),线程独占部分(虚拟机栈,本地方法栈,程序计数器)

JAVA的内存模型(概念)

针对多核多CPU,多线程而制定的一套规范规则,不是一种开发技术。

(二)多线程中的问题

所见非所得(你看到的并不是所想的)、

无法肉眼去检测程序的准确性(多线程下,完全看不出来正常不正常)。

不同的运行平台有不同的表现。

错误很难重现。

(三)工作内存和主内存

主内存

创建一个对象在堆里面,也可以称之为主内存,不仅仅是在堆,存在一个对象X,就存在主内存

工作内存

线程运行在工作内存, 虚拟机栈,程序计数器,CPU,高速缓存。

工作内存和主内存只是一个逻辑上的划分,概念上的东西。

奇妙的现象

主内存的flag传输到工作内存flag的时候,存在CPU缓存的情况,CPU缓存可能导致非常短的时间内不一致,本身CPU厂家底层是要做一致处理的,但是存在短时间内的不一致。

(四)指令重排

介绍

Java语言规范JVM线程内部维持顺序或语义,即只要程序的最终结果与它顺序化情况的结果相等,那么指令的执行顺序可以与代码逻辑顺序不一致,这个过程就叫做指令的重排序。

意义

使指令更加符合CPU的执行特性,最大限度的发挥机器的性能,提高程序的执行效率。

重排序,只能保证单个线程的,如果是多线程的话,就没有爆发保证重排序。

java // 线程1 a = d; b = 2 // 线程2 c = a; d =3 //重排序后 //线程1 b = 2 ; a =d; //线程2 d = 3 ; c =a; ¨G0G java public class VisibilityDemo2 { // 状态标识 (不用缓存) private volatile boolean flag = true; ¨K38K } ¨G1G java import java.util.concurrent.TimeUnit; public class VisibilityDemo1 { // 状态标识 private static boolean is = true; ¨K39K } ¨G2G java public class VisibilityDemo { private volatile boolean flag = true; ¨K40K }

启动线程的操作与线程中的第一个操作同步

对于每个属性写入默认值(0,false,null)与 每个线程对其操作的同步

线程T1的最后操作与线程T2发现线程T1已经结束同步(isAlive,join可以判断线程是否终结)

如果线程T1终端了T2,那么线程T1的中断操作与其他所有线程发现T2倍中断了同步,通过抛出InterruptedException异常,或者调用Thread.interrupted 或者 Thread.isInterrupted。

(八)Happyens-before先行发生原则

介绍

强调两个有冲突的动作之间的顺序,以及定义数据征用的发生时机。

原则

同一个线程里面对数据做了变动,后面的动作可以及时的看到,其实还是可见性。

某个monitor上的unlock动作 happens-before 同一个monitor上后续的lock动作。

对某个volatile 字段的写操作 happens-before 每个后续对该 volatile 字段的读操作。

在某个线程对象上调用start() 方法 happens-before 该启动了的线程中的任意动作。

某个线程中的所有动作 happens-before 任意其他线程成功从该线程对象上的join() 中返回。

如果某个动作 a 在happens-before 动作 b,b 在happens-before 动作 c,则 a happens-before c。

(九) final 在JMM中的处理

final在该对象的构造函数中设置对象的字段,当线程看到该对象时,将始终看到该对象的final字段的正确构造版本。

如果在构造函数中设置字段后发生读取,则会看到该final字段分配的值,否则它将看到默认值。

读取该共享对象的final成员变量之前,先要读取共享对象。

通常static final 是不可以修改的字段。然而System.in, System.out 和 System.err 是static final 字段,遗留原因,必须允许通过set方法改变,这些字段称为写保护,以区别于普通的final字段。

PS:使用了volatile,unlock和lock的时候,就可以保证代码不进行重排序。内存模型java进阶的一个核心点,这个理解了,其实比写多少年的业务代码要重要很多。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200702A02QZH00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券