7、volatile

https://www.cnblogs.com/fengzheng/p/9070268.html

 

 

并发的三个特性

并发场景三个特性:原子性、可见性、有序性;只有在满足了这三个特性,才能保证并发程序正确执行,否则会出现各种问题;

1、原子性:cas(乐观锁)和atomic*类,可以保证简单操作的原子性,对于一些负责的操作,可以使用synchronized或者各种锁来实现。

2、可见性:当多个线程访问同一个变量的时候,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。

3、有序性,程序执行的顺序按照代码的先后顺序执行,禁止进行指令重排序,看似理所当然的事情,其实并不是这样,指令重排序是jvm为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能的提高并行度,有些代码的顺序改变,有可能引起逻辑上的不正确。

而volatile 实现了两个特性,可见性和有序性,所以说在多线程环境中,需要保证这两个特性的功能,可以使用volatile关键字;

 

volatile是如何保证可见性的;

说到可见性,就要了解一下计算机的处理器和主存储器了,因为多线程,不管有多少个线程,最后还是要在计算机处理器中进行的,现在的计算机基本都是多核的,甚至有的机器是多处理器的,我们看一下多处理器的结构图:

 

 

core1 。 l1cache 。 l2cache 。 l3cache是插槽共享的; 然后是qpi 总线 mc、 主内存

这是两个处理器;四核的cpu,一个处理器对应一个物理插槽,多处理器间通过qpi总线相连。一个处理器包含多个核,一个处理器间的多核共享l3 cache,一个核包含寄存器,l1 l2 cache。

 

 

在程序执行的过程中,一定要设计到数据的读和写,而我们知道,虽然内存的访问速度已经很快了,但是总比齐cpu执行指令的速度来,还是差得很远。因此在内核中, 增加了l1 ,l2,l3三级缓存,这样一来,当程序运行的时候,现将所需要的数据从主存复制一份到所在核的缓存中。运算完成以后,在写入主存中,下图是cpu访问数据的示意图。由寄存器到高速缓存在到主存甚至硬盘的速度是越来越慢的。

寄存器 1cycle - 1ns

l1 高速缓存 。 3 cycle ~ 1ns

l2高速缓存 。 12cycle ~3ns

了解了cpu结构之后,我们来看一下程序执行的具体过程,那一个简单的 自增操作举例。

i=i+1;

执行这条语句的时候,在某个核上运行的某线程将i的值拷贝一个副本到此核所在的缓存中,当运算执行完成后,在回写到主存中去,如果是多线程环境下,每一个线程都会在所运行的核上的高速缓存区有一个

对应的工作内存,也就是每一个线程都有自己的私有工作缓存区,用来存放运算需要的副本数据,那么我们再来看这个i+1的问题; 假设i的初始值=0; 有两个线程同事执行这条语句,每个线程都需要三个步骤;

1、从主存读取i值到线程工作内存,也就是对应的内核高速缓存区;

2、计算i+1的值;

3、将结果值写回主存中;

假设两个线程各执行1万次后,我们预期是2万 才对,很遗憾i的值小于2万;导致这个问题的其中一个患有就是缓存一执行的问题;一旦某个线程的缓存副本做了修改,其他线程副本应该立即失效才对。

而使用了volatile关键字后,会有如下效果。

1、每次对变量的修改,都会引起处理器缓存(工作内存)写回到主存。

2、一个工作内存回写到主存会导致其他线程的处理器缓存(工作内存)无效。

因为volatile保证内存的可见性,其实是用到了cpu保证缓存一致性的mesi协议。mesi协议内容较多,这里就不做说明,请各位同学自己去查下一下,总之用了volatile关键字,当某线程对volatile变量会立即写回主内存;

其他线程强制从主内存读;

volatile并不保证原子性; 自增操作分三步; 假设线程1 从主存读i; 但是之后发生阻塞; 就发会发幻读;

 

volatile是如何保证有序的

java 内存模型具备一些先天的有序性;即不需要通过任何手段就能够得到保证的有序性;这个通常也称为 happend-before原则。

 

 

如下是happens-before的8条原则, 摘自 深入理解java虚拟机

程序次序规则 一个线程中, 按照代码顺序, 书写在前面的操作先行 发售于书写在后面的操作

锁定规定 。 一个unlock操作先行发生于后面对同一个锁的lock操作。

volatile变量规则,对一个变量的写操作 先行发生于后面对这个变量的读操作。

传递规则 。 如果操作a 先行发生于操作b; 而操作b又先行发生于操作c; 那么可以得出操作a先行发生于操作c;

线程启动规则 。 thread对象的start方法先行发生于此线程的每一个动作。

线程中断规则 。 对线程interrupt方法的调用先行发生于被中断线程的代码检测到中断时间的发生。

 

 

 

而使用volatile关键字,也就是使用了对一个volotile修饰的变量的写, happens-before于任何后续对该变量的读,这一原则, 对于到上面的初始化过程, 步骤2 和步骤3 都是对instance的写, 所以一定发生于后面对instance的读,也就是不会出现返回不完全初始化的instance这种可能。

jvm底层通过一个叫做内存屏障的东西来完成,内存屏障,也叫做内存栅栏, 是一组处理器指令, 用于实现对内存操作的顺序限制。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值