码迷,mamicode.com
首页 > 编程语言 > 详细

图灵学院java架构师vip

时间:2020-03-24 00:42:21      阅读:458      评论:0      收藏:0      [点我收藏+]

标签:区域   填充   基础   失效   jmm   dash   架构师   更新   nbsp   

caier-20 我们一起努力,一起学习,加油! caier-20

微云学习地址:https://share.weiyun.com/5mokPqU

网盘学习地址:https://pan.baidu.com/s/1CTx5SqUeM-ZKtDYLeovODQ 提取码:iclq

致学者:不论你在什么时候开始,重要的是开始之后请不要停止。

ava Memory Model,简称JMM,把它抽象成一种规范,用工作内存与主内存这两个概念。在JMM中主内存属于共享数据区域,从某个程度上讲应该包括了堆和方法区,而工作内存数据线程私有数据区域,从某个程度上讲则应该包括程序计数器、虚拟机栈以及本地方法栈。
主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,前面说过,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存完成。

2. 八大原子操作
我们已经清楚,每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据,线程与主内存中的变量操作必须通过工作内存间接完成,主要过程是将变量从主内存拷贝到每个线程各自的工作内存空间,然后对变量进行操作,操作完成后在某个不确定时刻再将变量写回主内存。这里再说具体一点,八大原子操作。

(1)lock(锁定):作用于主内存的变量,把一个变量标记为一条线程独占状态
(2)unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
(3)read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
(4)load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
(5)use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎 (6)assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量
(7)store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
(8)write(写入):作用于工作内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中

3. 可见性/原子性/有序性

可见性/原子性/有序性是并发编程的三大特性。前面1,2点相当于对第一篇文章做了个复习和补充,接下来分析一下实实在在的代码。

 

4. volatile关键字
volatile关键字就是第一篇文章说的缓存一致性协议的一种实现方式,缓存一致性协议就是volatile关键字的方法论。

(1)保证可见性
volatile关键字保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值立即被其他的线程看到,即修改的值立即更新到主存中,当其他线程需要读取时,它会去内存中读取新值。(synchronized和Lock也可以保证可见性,因为它们可以保证任一时刻只有一个线程能访问共享资源,并在其释放锁之前将修改的变量刷新到内存中。)

那我们再来分析下。在上面的例子中,如果initFlag用volatile修饰,线程A把标志位刷新了,true值立即更新到主存中,线程B工作内存中的值同时失效。那线程B再想读,就只能从主存中去取,于是能正常停止。

(2)保证有序性
先说说概念。举个例子,如果变量i用volatile修饰:volatile int i=0;
i=10; //这叫volatile写
另一个变量=i; //这叫volatile读
不用volatile修饰的变量的读写叫普通读写

5. synchronized关键字
volatile关键字也没有保证原子性。
实际上,所有的并发模式在解决线程安全问题时,采用的方案都是序列化(一个一个来)访问临界资源(多个线程同时访问同一个共享、可变资源的情况, 这个资源我们称之其为临界资源)。即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。synchronized关键字是同步互斥访问方法之一(还有Lock,之后章节会手撕源码)。
怎么使用这些基础就不讲了,直接讲原理。synchronized关键字被编译成字节码后会被翻译成monitorenter和monitorexit两条指令分别在同步块逻辑代码的起始位置与结束位置。

那么这个是干嘛的呢?让每个线程在进入同步代码块或是同步方法时,去这个同步对象的对象头里去找monitor指针,目的是找到monitor对象(每个对象在创建时都会有一个与它对应的monitor对象),看里面的信息,判断可不可以申请到对象的锁。

(1)对象头是什么?——了解对象的内存布局
HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头 (Header)、实例数据(Instance Data)和对齐填充(Padding)。
对象头:比如 hash码,对象所属的年龄,对象锁,锁状态标志,偏向锁(线程)ID,偏向时间,数组长度(数组对象才有)等
实例数据:即创建对象时,对象中成员变量,方法等
对齐填充:对象的大小必须是8字节的整数倍

(2)偏向锁:如果线程1申请锁,获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。如果线程2想要访问同步代码块了,会尝试修改一下对象头里面的线程id为自己(想让这个对象偏向自己),可能修改成功(线程1已经释放锁);可能失败(线程1还占有着锁),这时线程2向虚拟机发出申请说,让线程1这次时间片用完了停一停,我要用了。

图灵学院java架构师vip

标签:区域   填充   基础   失效   jmm   dash   架构师   更新   nbsp   

原文地址:https://www.cnblogs.com/qq2365217564/p/12556204.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!