共计 2623 个字符,预计需要花费 7 分钟才能阅读完成。
谈到并发编程就不得不提到并发三要素:原子性、可见性、有序性,而 Volatile 就会波及到可见性与有序性,可见 Volatile 在并发编程的重要的位置。
所以须要重点把握 Volatile,为了助大家把握好 Volatile,我会重点讲到以下 5 点:
1.Volatile 关键字
2.Java 内存模型
3.Volatile 内存模型可见性
4.Volatile 的工作原理
5.Volatile 的源码案例
在谈 Volatile 之前,咱们先回顾下 Java 内存模型的三要素:原子性、可见性、有序性,也就是大家常提到的并发编程三要素。
并发编程的三要素
1. 原子性
和数据库事务中的原子性一样,满足原子性个性的操作是不可中断的,要么全副执行胜利要么全副执行失败
只有简略的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的互相赋值不是原子操作)才是原子操作。
比方:i = 2;j = i;i++;i = i + 1;
下面 4 个操作中,i= 2 是读取操作,必然是原子性操作,j= i 你认为是原子性操作,其实吧,分为两步,一是读取 i 的值,而后再赋值给 j, 这就是 2 步操作了,称不上原子操作,i++ 和 i = i + 1 其实是等效的,读取 i 的值,加 1,再写回主存,那就是 3 步操作了。
所以下面的举例中,最初的值可能呈现多种状况,就是因为满足不了原子性。
非原子操作都会存在线程平安问题,须要咱们应用同步技术(sychronized)来让它变成一个原子操作,java 的 concurrent 包下提供了一些原子类:比方:AtomicInteger、AtomicLong 等。
2. 可见性
多个线程拜访同一个共享变量时,其中一个线程对这个共享变量值的批改,其余线程可能立即取得批改当前的值
3. 有序性
编译器和处理器为了优化程序性能而对指令序列进行重排序,也就是你编写的代码程序和最终执行的指令程序是不统一的。
然而重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
Volatile
Volatile 是一个 Java 语言的类型修饰符,一旦一个共享变量(类的成员变量、类的动态成员变量)被 Volatile 润饰之后,那么就具备了两层语义:
1、保障多线程下的可见性
2、禁止进行指令重排序(即保障有序性)
这里须要留神一个问题,Volatile 只能让被他润饰内容具备可见性、有序性。
Volatile 只能保障对单次读 / 写的原子性,i++ 这种操作不能保障原子性。
Volatile 的内存模型
Java 内存模型(JMM)是一种形象的概念,并不实在存在,它形容了一组规定或标准,通过这组标准定义了程序中各个变量(包含实例字段、动态字段和形成数组对象的元素)的拜访形式。
试图屏蔽各种硬件和操作系统的内存拜访差别,以实现让 Java 程序在各种平台下都能达到统一的内存拜访成果。
Java 内存模型规定了所有的变量都存储在主内存中,每条线程还有本人的工作内存,线程的工作内存中保留了该线程中是用到的变量的主内存正本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能间接读写主内存。不同的线程之间也无奈间接拜访对方工作内存中的变量,线程间变量的传递均须要本人的工作内存和主存之间进行数据同步进行。
主内存次要存储的是 Java 实例对象,所有线程创立的实例对象都寄存在主内存中,不论该实例对象是成员变量还是办法中的本地变量 (也称局部变量),当然也包含了共享的类信息、常量、动态变量。因为是共享数据区域,多条线程对同一个变量进行拜访可能会发现线程平安问题。
工作内存每条线程都有本人的工作内存(Working Memory,又称本地内存,可与后面介绍的处理器高速缓存类比),线程的工作内存中保留了该线程应用到的变量的主内存中的共享变量的正本拷贝。工作内存是 JMM 的一个抽象概念,并不实在存在。它涵盖了缓存,写缓冲区,寄存器以及其余的硬件和编译器优化。
次要存储以后办法的所有本地变量信息(工作内存中存储着主内存中的变量正本拷贝),每个线程只能拜访本人的工作内存,即线程中的本地变量对其它线程是不可见的,就算是两个线程执行的是
一段代码,它们也会各自在本人的工作内存中创立属于以后线程的本地变量,当然也包含了字节码行号指示器、相干 Native 办法的信息。
Volatile 的实现原理
Volatile 保障内存可见性
主内存和工作内存之间的交互有具体的交互协定,JMM 定义了八种操作来实现,这八种操作是原子的、不可再分的,它们别离是:lock,unlock,read,load,use,assign,store,write,其中 lock,unlock,read,write 作用于主内存;load,use,assign,store 作用于工作内存。
(1) lock: 将主内存中的变量锁定,为一个线程所独占
(2) unclock: 将 lock 加的锁定解除,此时其它的线程能够有机会拜访此变量
(3) read: 将主内存中的变量值读到工作内存当中
(4) load: 将 read 读取的值保留到工作内存中的变量正本中。
(5) use: 将值传递给线程的代码执行引擎
(6) assign: 将执行引擎解决返回的值从新赋值给变量正本
(7) store: 将变量正本的值存储到主内存中。
(8) write: 将 store 存储的值写入到主内存的共享变量当中。
从主存复制变量到当前工作内存(read and load)
执行代码,扭转共享变量值(use and assign)
用工作内存数据刷新主存相干内容(store and write)
指令规定
- read 和 load、store 和 write 必须成对呈现
- assign 操作,工作内存变量扭转后必须刷回主内存
- 同一时间只能运行一个线程对变量进行 lock,以后线程 lock 可重入,unlock 次数必须等于 lock 的次数,该变量能力解锁。
- 对一个变量 lock 后,会清空该线程工作内存变量的值,从新执行 load 或者 assign 操作初始化工作内存中变量的值。
- unlock 前,必须将变量同步到主内存(store/write 操作)
Volatile 源码案例
以上就是 Java 并发编程之 Volatile 的实现原理介绍,心愿对你有所播种!
-END-
对于作者:mikechen,十余年 BAT 架构教训,资深技术专家,曾任职阿里、淘宝、百度。
关注集体公众号:mikechen 的互联网架构,十余年 BAT 架构教训倾囊相授!
在公众号菜单栏对话框回复 【架构】 关键词,即可查看我 原创的 300 期 +BAT 架构技术系列文章与 1000+ 大厂面试题答案合集。