volatile 知识点详解

Posted shallowpen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了volatile 知识点详解相关的知识,希望对你有一定的参考价值。

总结:

  1、保证可见性(JMM)

  2、不保证原子性 (核心难点:原子类)

  3、禁止指令重排 (核心难点:说出单例模式。说出CAS。说出CPU原语)

技术图片

 

 

 1 // 验证volatile 可见性
 2 public class JmmDemo {
 3     private volatile static int num = 0; // 不加volatile,程序会一直运行
 4     public static void main(String[] args) throws InterruptedException {
 5         new Thread(() -> {
 6             while (num==0){
 7 
 8             }
 9         }).start();
10         TimeUnit.SECONDS.sleep(1L); // 睡眠一秒钟,保证进入while循环
11         num = 1;
12         System.out.println(Thread.currentThread().getName()+num);
13     }
14 }
 1 // 验证volatile 不保证原子性
 2 public class JmmDemo2 {
 3     // 不用synchronized lock锁来保证原子性
 4     // 使用AtomicInteger原子类
 5     private static AtomicInteger num = new AtomicInteger();
 6 
 7     public static void add() {
 8         num.getAndIncrement(); // 相当于num++
 9     }
10 
11     /*
12     // 验证volatile 不保证原子性
13     private static  int num = 0;
14     // synchronized
15     public synchronized static void add() {
16         num++;
17     }
18 */
19     public static void main(String[] args) {
20         // 期望 num 最终是2万
21         for (int i = 0; i < 20; i++) {
22             new Thread(() ->{
23                 for (int j = 0; j < 1000; j++) {
24                     add();
25                 }
26             }).start();
27         }
28 
29         // 判断活着的线程
30         while (Thread.activeCount() > 2) {
31             Thread.yield();
32         }
33 
34         System.out.println(Thread.currentThread().getName()+ num);
35     }
36 }
 1 // 指令重排理论
 2 // int x = 1;  // 1
 3 // int y = 1;  // 2
 4 // x = x + 5;  // 3
 5 // y = x * x;  // 4
 6 // 可重排 1234 1324 2134 2314 3124 3214
 7 // 4可能开头吗? 如4312.
 8 // 不可能  因为处理器在重排的时候也会考虑指令间的依赖性
 9 
10 // 尝试理解多线程下的指令重排问题:
11 // int x,y,a,b = 0;
12 // 线程A              线程B
13 // x = a;           y = b;
14 // b = 1;           a = 2;
15 // 理想的结果下x = 0, y = 0
16 
17 // 指令重排后
18 // 线程A              线程B
19 // b = 1;           a = 2;
20 // x = a;           y = b;
21 // 重排后的结果:x = 2,y = 1
22 
23 // volatile可以禁止指令重排
24 // 内存屏障 :CPU指令 两个作用:
25 // 1、保证特定的执行顺序
26 // 2、保证某些变量的内存可见性
27 
28 //         普通写
29 //         普通读
30 // ---------------------内存屏障,禁止上面普通写与volatile读指令重排
31 //         volatile读
32 // ---------------------内存屏障,禁止下面普通写与volatile读指令重排
33 //         普通写
34 //         普通读
35 // 指令重排的最经典应用 DCL单例模式   DCL双重检测锁
36 // 单例的思想: 构造器私有
37 // 单例之所以安全,是因为构造器私有的!

 

单例模式:

 1 // 单例的思想: 构造器私有
 2 // 饿汉式
 3 public class Hungry {
 4     // 浪费空间
 5     private byte[] data = new byte[10*1024*1024];
 6 
 7     private Hungry(){}
 8 
 9     private final static Hungry HUNGRY = new Hungry();
10 
11     public static Hungry getInstance() {
12         return HUNGRY;
13     }
14 }

 

 1 // 懒汉式 (基础的)
 2 public class LazyMan {
 3     private LazyMan(){
 4         System.out.println(Thread.currentThread().getName()+"__测试");
 5     }
 6 
 7     private static LazyMan lazyMan;
 8 
 9     public static LazyMan getInstance(){
10         if (lazyMan == null) {
11             lazyMan = new LazyMan();
12         }
13         return lazyMan;
14     }
15 
16     public static void main(String[] args) {
17         new Thread(() -> {
18             LazyMan.getInstance();
19         }).start();
20 
21         // 多线程下单例失效
22         /*for (int i = 0; i < 10; i++) {
23             new Thread(() -> {
24                 LazyMan.getInstance();
25             }).start();
26         }*/
27     }
28 }

 

 1 // DCL懒汉式
 2 public class DclLazyMan {
 3     
 4     // 防止反射破环单例
 5     private static boolean ahsdkhaskhdkahksh = false;
 6     private DclLazyMan() {
 7         synchronized (DclLazyMan.class){
 8             if (ahsdkhaskhdkahksh == false) {
 9                 ahsdkhaskhdkahksh = true;
10             } else {
11                 throw new RuntimeException("不要想破坏我的单例");
12             }
13         }
14     }
15     private volatile static DclLazyMan dclLazyMan;
16 
17     public static DclLazyMan getInstance() {
18         if (dclLazyMan == null) {
19             synchronized (DclLazyMan.class) {
20                 if (dclLazyMan == null) {
21                     dclLazyMan = new DclLazyMan();// 这个操作是不是原子性的? 不是
22                     // java创建一个对象
23                     // 1、分配内存空间
24                     // 2、执行构造方法,创建对象
25                     // 3、将对象指向空间
26 
27                     // A  先执行13,这个时候对象还没有完成初始化!
28                     // B  发现对象为空,B线程拿到的对象就不是完整的
29                 }
30             }
31         }
32         return dclLazyMan;
33     }
34 
35     public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
36         /*
37         // 经过构造器
38         DclLazyMan dclLazyMan1 = DclLazyMan.getInstance();
39 
40         // 利用反射,不经过构造器破坏单例
41         Constructor<DclLazyMan> constructor = DclLazyMan.class.getDeclaredConstructor();
42         DclLazyMan dclLazyMan2 = constructor.newInstance();
43 
44         System.out.println(dclLazyMan1.hashCode());
45         System.out.println(dclLazyMan2.hashCode());
46         */
47 
48         Constructor<DclLazyMan> constructor = DclLazyMan.class.getDeclaredConstructor();
49         constructor.setAccessible(true);
50         DclLazyMan dclLazyMan2 = constructor.newInstance();// 创建对象
51         
52         // 利用编译的.class文件拿到ahsdkhaskhdkahksh
53         // 单例再次被破坏
54         Field ahsdkhaskhdkahksh = DclLazyMan.class.getDeclaredField("ahsdkhaskhdkahksh");
55         ahsdkhaskhdkahksh.setAccessible(true);
56         ahsdkhaskhdkahksh.set(dclLazyMan2,false);
57 
58         System.out.println(dclLazyMan2.hashCode());
59 
60     }
61 }
 1 // enum类型
 2 public enum SingleEnum {
 3     INSTANCE;
 4     public SingleEnum getInstance(){
 5         return INSTANCE;
 6     }
 7 }
 8 // 至少在做一个普通的JVM时候,jdk源码没有被修改的时候,枚举就是安全的!
 9 class Demo{
10     public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException,
11             InvocationTargetException, InstantiationException, InvocationTargetException {
12         
13         // String.class,int.class
14         // 利用jad.exe反编译.class文件,发现反编译出的.java文件中没有无参构造器
15         Constructor<SingleEnum> declaredConstructor = SingleEnum.class.getDeclaredConstructor(String.class,int.class);
16         declaredConstructor.setAccessible(true);
17         // throw new IllegalArgumentException("Cannot reflectively create enum objects");
18         SingleEnum singleEnum1 = declaredConstructor.newInstance();
19         SingleEnum singleEnum2 = declaredConstructor.newInstance();
20         System.out.println(singleEnum1.hashCode());
21         System.out.println(singleEnum2.hashCode());
22 
23         // 这里面没有无参构造! JVM 才是王道!
24         // "main" java.lang.NoSuchMethodException: com.coding.single.SingleEnum.<init>()
25     }
26 }

 

CAS :(Compare and swap) 比较并交换

  缺点:

  1. 循环开销很大
  2. 内存操作,每次只能保证一个共享变量的原子性
  3. 出现ABA问题
 1 // CAS :(Compare and swap) 比较并交换
 2 public class CAS_Demo {
 3     public static void main(String[] args) {
 4         // 默认值为0
 5         AtomicInteger atomicInteger = new AtomicInteger(5);
 6 
 7         // 期望值5,改为20
 8         System.out.println(atomicInteger.compareAndSet(5, 20));
 9         System.out.println(atomicInteger.get());
10 
11         // 期望值为5,改为6
12         System.out.println(atomicInteger.compareAndSet(5, 6));
13         System.out.println(atomicInteger.get());
14     }
15 
16 //----------------------------------------------------------------
17 
18 //    private static AtomicInteger num = new AtomicInteger();
19 //    public static void add() {
20 //        num.getAndIncrement(); // 相当于num++
21 //    }
22 
23 // 以getAndIncrement()分析理解 CAS
24 
25 // 源码
26 //    public final int getAndIncrement() {
27         // unsafe 可以直接操作内存
28             // this 调用的对象
29             // valueOffset 当前这个对象的值内存地址偏移值
30             // 1  +1
31 //        return unsafe.getAndAddInt(this, valueOffset, 1);
32 //    }
33 //
34 //    public final int getAndAddInt(Object var1, long var2, int var4) {
35 //        int var5; //36 //        do { // 自旋锁(就是一只判断!)
37                 // var5 = 获得当前对象的内存地址中的值!
38                 // var1 等于上面的this
39                 // var2 等于上面的valueOffset
40 //            var5 = this.getIntVolatile(var1, var2);
41                 // compareAndSwapInt 比较并交换
42                 // 比较当前的值var1 对象的var2 地址中的值是不是var5,如果是,则更新为var5+1(var4)
43                 // 如果是期望的值,就交换,否则就不交换
44 //        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
45 //
46 //        return var5;
47 //    }
48 }

 

原子引用----解决ABA问题

 1 // 原子引用----解决ABA问题
 2 // 通过增加一个版本号来解决,和乐观锁一模一样
 3 
 4 // ABA问题描述:
 5 // T1 将A 改为 B 再改为 A  数据已经被修改过两次
 6 // T2 再来操作A 但是A不是当初的A了  因为数据被改过了两次
 7 public class ABA_Demo {
 8     // 设置初始版本为1
 9     static AtomicStampedReference<Integer> asf = new AtomicStampedReference<>(100,1);
10 
11     public static void main(String[] args) {
12         // 线程T1
13         new Thread(() -> {
14             // 获得当前版本
15             int stamp = asf.getStamp();
16             System.out.println("T1当前版本:"+stamp);
17             try {
18                 // 睡眠1秒 待T2拿到stamp
19                 TimeUnit.SECONDS.sleep(1L);
20             } catch (InterruptedException e) {
21                 e.printStackTrace();
22             }
23             // 即 A 改为 B
24             asf.compareAndSet(100,101,asf.getStamp(),asf.getStamp()+1);
25             System.out.println("T1 当前版本:"+asf.getStamp());
26             System.out.println("T1 当前获得的最新值:"+asf.getReference());
27             // 即 B 改为 A
28             asf.compareAndSet(101,100,asf.getStamp(),asf.getStamp()+1);
29             System.out.println("T1 当前版本:"+asf.getStamp());
30             System.out.println("T1 当前获得的最新值:"+asf.getReference());
31 
32         },"T1").start();
33 
34         // 线程T2
35         new Thread(() -> {
36             // 获得当前版本
37             int stamp = asf.getStamp();
38             System.out.println("T2当前版本:"+stamp);
39             try {
40                 // 睡眠2秒,待T1执行完
41                 TimeUnit.SECONDS.sleep(2L);
42             } catch (InterruptedException e) {
43                 e.printStackTrace();
44             }
45             // 即 A 改为 B
46             boolean result = asf.compareAndSet(100, 101, stamp, stamp + 1);
47             System.out.println(Thread.currentThread().getName()+"是否修改成功:"+result);
48             System.out.println("T2 当前版本:"+asf.getStamp());
49             System.out.println("T2 当前获得的最新值:"+asf.getReference());
50         },"T2").start();
51     }
52 }   

以上是关于volatile 知识点详解的主要内容,如果未能解决你的问题,请参考以下文章

volatile详解

Java基础:volatile详解

Java并发编程--Volatile详解

362volatile底层原理详解

volatile详解

volatile详解