1- # 万字长文 | 我想跟你聊一宿的Java volatile | 附示例
1+ # 万字长文 | 再也不怕面试官问 volatile!!!
22
33# 一、Volatile怎么念?
44
@@ -304,7 +304,7 @@ number++其实执行了`3条指令`:
304304
305305## 7.1 synchronized同步代码块
306306
307- 我们可以通过使用synchorized同步代码块来保证原子性 。从而使结果等于20000
307+ 我们可以通过使用synchronized同步代码块来保证原子性 。从而使结果等于20000
308308
309309``` java
310310public synchronized static void increase() {
@@ -360,11 +360,11 @@ public static void main(String[] args) {
360360
361361会不会感觉到重排把指令顺序都打乱了,这样好吗?
362362
363- 可以回想下小学时候的数学题:2+3-3=?,如果把运算顺序改为3-3+2 ,结果也是一样的。所以指令重排是要保证程序结果不变的情况下做重排 。
363+ 可以回想下小学时候的数学题:` 2+3-5=? ` ,如果把运算顺序改为 ` 3-5+2=? ` ,结果也是一样的。所以指令重排是要保证单线程下程序结果不变的情况下做重排 。
364364
365365## 8.1 为什么要重排
366366
367- 计算机在执行程序时,在执行程序时, 为了提高性能,编译器和处理器常常会对指令做重排序。
367+ 计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。
368368
369369## 8.2 有哪几种重排
370370
@@ -384,39 +384,88 @@ public static void main(String[] args) {
384384
385385- 多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。
386386
387- # 、volatile什么场景下会用到 ?
387+ ## 8.3 举个例子来说说多线程中的指令重排 ?
388388
389- 奇怪的是,volatile都不保证原子性,为啥我们还要用它?
389+ 设想一下这种场景:定义了变量num=0和变量flag=false,线程1调用初始化函数init()执行后,num=1,flag=true,当另外线程判断flag=true后,执行num+100操作,那么我们预期的结果是num会等于101,但因为有指令重排的可能,num=1和flag=true执行顺序可能会颠倒,以至于num可能等于100
390390
391- volatile是轻量级的同步机制,对性能的影响比synchorized小。
391+ ``` java
392392
393- > 典型的用法:检查某个状态标记以判断是否退出循环。
394393
395- 比如线程试图通过类似于数绵羊的传统方法进入休眠状态,为了使这个示例能正确执行,asleep必须为volatile变量。否则,当asleep被另一个线程修改时,执行判断的线程却发现不了。
394+ num= 1 ;
395+ flag = true ;
396396
397- ** 那为什么我们不直接用synchorized,lock锁?它们既可以保证可见性,又可以保证原子性为何不用呢? **
397+ 的执行顺序变为
398398
399- 因为synchorized和lock是排他锁(悲观锁),如果有多个线程需要访问这个变量,将会发生竞争,只有一个线程可以访问这个变量,其他线程被阻塞了,会影响程序的性能。
399+ flag = true ;
400400
401- > 注意:当且仅当满足以下所有条件时,才应该用volatile变量
402- >
403- > - 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
404- > - 该变量不会与其他的状态一起纳入不变性条件中。
405- > - 在访问变量不需要加锁。
401+ num= 1 ;public class VolatileResort {
406402
403+ static int num = 0 ;
404+ static boolean flag = false ;
407405
406+ public static void init () {
407+ num= 1 ;
408+ flag = true ;
409+ }
408410
411+ public static void add () {
412+ if (flag) {
413+ num = num + 5 ;
414+ System . out. println(" num:" + num);
415+ }
416+ }
409417
418+ public static void main (String [] args ) {
419+ init();
420+ new Thread (() - > {
421+ add();
422+ }," 子线程" ). start();
423+ }
424+ }
425+ /*
426+ num= 1;
427+ flag = true;
428+ if (flag) {
429+ num = num + 5;
430+ System.out.println("num:" + num);
431+ }
432+ 的执行顺序变为
433+
434+ flag = true;
435+ if (flag) {
436+ num = num + 5;
437+ System.out.println("num:" + num);
438+ }
439+ num= 1;
440+ */
441+ ```
410442
411443
444+
412445
413- # 、主线程是如何立即感知到变量变了呢?
414446
415447
448+ # # 十、volatile什么场景下会用到?
416449
450+ 奇怪的是,volatile都不保证原子性,为啥我们还要用它?
417451
452+ volatile是轻量级的同步机制,对性能的影响比synchronized小。
418453
454+ > 典型的用法:检查某个状态标记以判断是否退出循环。
455+
456+ 比如线程试图通过类似于数绵羊的传统方法进入休眠状态,为了使这个示例能正确执行,asleep必须为volatile变量。否则,当asleep被另一个线程修改时,执行判断的线程却发现不了。
457+
458+ ** 那为什么我们不直接用synchorized,lock锁?它们既可以保证可见性,又可以保证原子性为何不用呢?**
459+
460+ 因为synchorized和lock是排他锁(悲观锁),如果有多个线程需要访问这个变量,将会发生竞争,只有一个线程可以访问这个变量,其他线程被阻塞了,会影响程序的性能。
461+
462+ > 注意:当且仅当满足以下所有条件时,才应该用volatile变量
463+ >
464+ > - 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
465+ > - 该变量不会与其他的状态一起纳入不变性条件中。
466+ > - 在访问变量不需要加锁。
419467
468+ # 十一、volatile和synchronized
420469
421470
422471
0 commit comments