栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

并发的原子性 可见性 有序性和classloader

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

并发的原子性 可见性 有序性和classloader

原子性

是指一个操作或多个操作要么全部执行,且执行的过程不会被任何因素打断,要么就都不执行。

原子操作原理(处理器是如何实现原子操作的)
处理器实现原子操作有3种方式:

1.处理器自动保证基本内存操作的原子性

首先说明,处理器会自动保证基本的内存操作是原子性的。处理器保证从系统内存中读取或写入一个字节是原子的。

当然,long和double类型在32位操作系统中的读写操作不是原子的,因为long和double占64位,需要分成2个步骤来处理,在读写时分别拆成2个字节进行读写。因此long和double类型的数据在进行计算时需要注意这个问题。

2.使用总线锁保证原子性

如果多个处理器同时对共享变量进行读改写操作,共享变量就会被多个处理器同时进行操作,这样读改写操作就不是原子的。

当多个处理器调度线程时,同时读取主内存中的共享变量,就会造成上面的问题。如果想上述操作是原子的,那么必须保证CPU1读改写共享变量时,CPU2不能做缓存该共享变量的操作。

处理器使用总线锁就是解决这个问题的。总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占使用共享内存。

可见性


当一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改。

Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性的,无论是普通变量还是volatile变量都是如此。

普通变量与volatile变量的区别
是volatile的特殊规则保证了新值能立即同步到主内存,以及每使用前立即从内存刷新。因为我们可以说volatile保证了线程操作时变量的可见性,而普通变量则不能保证这一点。

volatile关键字介绍
在java中提供了volatile关键字,通过volatile关键字修饰内存中的变量,该变量在线程之间共享。

volatile关键字是轻量级的锁(synchronized)。在使用的时候,消耗的成本比synchronized小很多。volatile用于修饰变量。

volatile实现原理
volatile修饰的变量,在翻译成汇编语言的时候,会有一个LOCK前缀的指令。

LOCK前缀的指令在多核处理器下会引发两件事情。

将当期处理器缓存行的数据会写回到系统内存。
这个写回内存的操作会引起在其他CPU里缓存该内存地址的数据无效。
如果对声明了volatile变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。

volatile的适用范围
volatile变量固然方便,但是存在着限制,volatile修饰的变量,并不能保证是原子操作的,所以多处理器操作数据时,会导致数据重复。所以volatile关键字通常被当作完成、中断的状态的标识使用。

final关键字的可见性
与volatile相比较,final域的读和写更像是普通的变量访问。

对于final域,编译器和处理器要遵守两个重排序规则:

在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序

写final域的重排序规则
JMM禁止编译器把final域的写重排序到构造函数之外。

编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造函数之外。

读final域的重排序规则

在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作(注意,这个规则仅仅针对处理器)。编译器会在读final域操作的前面插入一个LoadLoad屏障。

有序性

即程序执行的顺序按照代码的先后顺序执行。

Java内存模型中的程序天然有序性可以总结为一句话:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存主主内存同步延迟”现象。

有序性的语意有几层,
1. 最常见的就是保证多线程运行的串行顺序
2. 防止重排序引起的问题
3. 程序运行的先后顺序。比方JMM定义的一些Happens-before规则

synchronized是如何保证有序性的(synchronized同步原理)
JVM规范规定JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明,但是方法的同步同样可以使用这两个指令来实现。

monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor 被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

当使用synchronized关键字是,只能有一个线程执行直到执行完成后或异常,才会释放锁。所以可以保证synchronized代码块或方法只会有一个线程执行,保障了程序的有序性。
 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/704377.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号