Java多线程 -- JUC包源码分析3-- volatile/final语义
来源:互联网 发布:天诚网络 编辑:程序博客网 时间:2024/04/29 01:18
volatile应用1 – 内存可见性 – JMM内存模型
-volatile应用2 – 原子性
-volatile应用3 – 构造函数逸出/DCL问题(Double Checking Locking)
-final应用1 – 避免构造函数重排序
-final应用2 – CopyOnWrite
-atomic数组/volatile数组/final数组/
-指令重排序,happen before语义
volatile应用1 – 内存可见性
在讲述抽象的理论之前,先看2个案例:
案例1:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
案例2:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
答案:在上面的例子里面,线程B未必能读到线程A写入的值。案例2有可能死循环。
这要从现代多CPU说起: 在现代的CPU架构中,每个CPU都会有自己的缓存(L1缓存,L2缓存。关于CPU缓存,后续会详细阐述,此处只是提及)。如图所示:
其对应的JVM的抽象内存模型JMM,如下图所示:
线程A,线程B有各自的local内存。在把变量从主内存读到自己的工作内存,修改之后,不一定会立即写入主存,因此另一个线程不可见。
要保证上述案例可以完全正确执行,需要在变量前加volatile。
volatile变量可以保证:每次对该变量的写,必定刷回到主存;每次对该变量的读,必定从主存读取。从而可以保证,一个线程对共享变量的写,对其他线程可见。
volatile应用2 – 原子性
案例3:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
案例3和案例1相比,只是int换成了long。
由于JMM并不要求对一个64位的long/double型的变量写入具有原子性,在32位的机器上,对一个long型变量的写入,可能会分成高32位,低32位2次写入。此时,另一个线程去读取时,可能读到“写了一半”的无效值!
要解决上述问题,可以加锁,也可以加volatile关键字。
可见,在对单个变量的读写中,volatile变量起到了锁同样的作用。
也正因为如此,在AtomicInteger/AtomicLong中,其get()/set()函数,都未加锁,却是线程安全的!!
volatile应用3 – 避免构造函数逸出/DCL问题
线程安全的单例模式中,有一种经典写法,即DCL(Doule Checking Locking),如下所示:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
上述的new Instance(),底层可以分为3个操作: 分配内存,在内存上初始化成员变量,把instance指向内存。
这3个操作,可能重排序,即先把instance指向内存,再初始化成员变量。
此时,另外一个线程就会拿到一个未完全初始化的对象。这时直接访问里面的成员变量,就可能出错。而这就是典型的“构造函数溢出”问题。
要解决此问题,只要在instance前加volatile就可以了!
当然,还有另外1种经典的线程安全的单例模式 – 基于类加载器的方案
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
final应用1 – 避免构造函数重排序
案例4
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
答案是:a, b 未必一定等于1,2。因为这里的i, j都是非volatile变量,线程A的重排序,可能使得i, j的赋值,在构造函数之后执行!!也就是说,线程B拿到obj的时候,obj的i, j变量可能赋值还未完成!
解决办法是:给i, j 加上final
final的语义: 保证final变量的初始化,一定在构造函数返回之前完成!
final应用2 – CopyOnWrite
在上1篇 NumberRange例子中,我们看到lower, power都是final类型,这也确保了lower, power只可能被赋值1次。后续要想再改变值,只能拷贝一份出来改!
所以,通常应用CopyOnWrite的地方,也会相应的使用final!
atomic数组/volatile数组/final数组
关于atomic,volatile, final的数组类型,很容易存在着如下误解:
AtomicIntegerArray, AtomicLongArray
只是说里面的每个元素是原子的,而不是整个数组是原子的!比如说,你一个for循环,set每1个值,这整个for循环,并不是原子的。
volatile数组
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
final 数组
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
指令重排序/happen before
从上述各种案例可以看出,问题主要出在“指令重排序”上。
为什么要指令重排序呢?
从程序员角度来讲,最好是不要有任何的指令重排,这样程序最容易理解;但从CPU和编译器角度,希望在不改变单线程程序语义的情况下,尽可能的重排序,最大程度的提高执行效率。
而对于多线程程序,因为重排序导致的线程之间的不同步,则由程序员自己处理!
volatile和final的底层原理,就是一定程度上禁止重排序,从而实现多线程程序的同步。
关于重排序和happen before的深入阐释,且看下回分解。
- Java多线程 -- JUC包源码分析3-- volatile/final语义
- Java多线程 -- JUC包源码分析3-- volatile/final语义
- Java多线程 -- JUC包源码分析10 -- ConcurrentLinkedQueue源码分析
- Java多线程 -- JUC包源码分析11 -- CyclicBarrier源码分析
- Java多线程 -- JUC包源码分析12 -- ThreadPoolExecutor源码分析
- Java多线程 -- JUC包源码分析16 -- Exchanger源码分析
- Java多线程 -- JUC包源码分析1 -- CAS/乐观锁
- Java多线程 -- JUC包源码分析6 -- ConcurrentHashMap
- Java多线程 -- JUC包源码分析15 -- SynchronousQueue与CachedThreadPool
- Java多线程 -- JUC包源码分析19 -- ForkJoinPool/ForkJoinTask
- Java多线程 -- JUC包源码分析1 -- CAS/乐观锁
- Java多线程 -- JUC包源码分析13 -- Callable/FutureTask源码分析
- Java多线程 -- JUC包源码分析14 -- ScheduledThreadPoolExecutor与DelayQueue源码分析
- Java多线程 -- JUC包源码分析9 -- AbstractQueuedSynchronizer深入分析-- Semaphore与CountDownLatch
- Java多线程 -- JUC包源码分析2 -- Copy On Write/CopyOnWriteArrayList/CopyOnWriteArraySet
- Java多线程 -- JUC包源码分析4 -- 各种锁与无锁
- Java多线程 -- JUC包源码分析5 -- Condition/ArrayBlockingQueue/LinkedBlockingQueue/Deque/PriorityBlockingQueue
- Java多线程 -- JUC包源码分析7 -- 对Interrupt的深刻理解
- springMVC传递List和数组到后端
- nodejs入门(07)-函数
- 《分水岭:看清中国科技和互联网未来五年的趋势》出版 腾讯科技出品
- SQL Server 2008R2密钥
- [POJ 1330][CodeVS 2370]LCA 倍增
- Java多线程 -- JUC包源码分析3-- volatile/final语义
- JavaHttp GET POST 请求(HttpURLConnection)
- 来,和我谈谈,关于接口和抽象类,那些你说不出口的事...
- Django学习笔记5-玩转API-Django数据库操作
- Python 读取配置文件
- 迷宫问题求解(用栈实现)
- word 2013 文档目录与正文用不同的页面下标方法
- 新生活,从点滴积累开始!
- jsonp实现跨域请求