JUC基础总结01 - volatile & cas

JUC基础总结01 - volatile & cas
    一、可见性的代码验证说明
    二、volatile指令重排案例
    三、CAS及ABA问题
    
一、可见性的代码验证说明
1.JMM,Java内存模型,本身是一种抽象概念并不真实存在。它描述的是一组规范,通过这个规范定义了程序中各变量的访问方式,是为了解决多核CPU缓存数据不一致问题提出的。
2.JMM关于同步的规定:
    1) 线程解锁前,必须把共享变量的值刷新回主内存
    2) 线程加锁前,必须读取主内存中最新值到自己的工作内存
    3) 加锁解锁是同一把锁
3.JVM运行程序的实体是线程,每个线程(对应多核CPU线程)创建时,JVM都会为其创建一个工作内存(栈空间),线程间的通信必须通过主内存完成
    
    
    主内存和工作内存之间的交互有具体的交互协议。对于交互协议,JMM定义了8种操作完成,分别是:lock、unlock、read、load、use、assign、store、write,其中:lock、unlock、read、write作用于主内存;load、use、assign、store作用于工作内存
    1) 从主内存复制到当前工作内存:read and load
    2) 执行代码,改变共享量值:use and assign
    3) 用工作内存数据刷新主内存:store and write
4.volatile 可见性实践

// volatile 是JVM提供的轻量级同步机制:保证可见性、禁止指令重排、不保证原子性
public class VolatileDemo extends Thread {

    public int number = 0;
    // public volatile int number = 0;

    @Override
    public void run() {
        System.out.println("线程开始...");
        while (number == 0) {
            // System.out.println("放开这个注释有意想不到的事发生...");
        }
        System.out.println("线程停止!");
    }

    public static void main(String[] args) {
        VolatileDemo thread = new VolatileDemo();
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(1);
            thread.number += 30;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main线程已修改共享变量为" + thread.number);
    }
}

意想不到的是,System.out.println方法中使用了 synchronized,即使没有使用volatile关键字,while循环时,不断取到number最新值

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

二、volatile指令重排案例
1.volatile 实现可见性和禁止指令重排,是依靠内存栅栏(一条CPU指令)完成
2.单例模式volatile分析

public class SingleTon01 {

    private static SingleTon01 singleTon01 = null;
    private SingleTon01() { }

    public static SingleTon01 getSingleTon01() {
        if (singleTon01 == null) {
            singleTon01 = new SingleTon01();
        }
        return singleTon01;
    }

    public static void main(String[] args) {
//        System.out.println(SingleTon01.getSingleTon01());
//        System.out.println(SingleTon01.getSingleTon01());
//        System.out.println(SingleTon01.getSingleTon01());
//        System.out.println(SingleTon01.getSingleTon01());
        // 多线程出现多个实例
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(SingleTon01.getSingleTon01());
            }, String.valueOf(i)).start();
        }
    }
}

    1) 解决方法1:在 getSingleTon01 方法上加 synchronized 关键字修饰
    2) 解决方法2:双重检测锁 DCL 版

public class SingleTon01 {

    private static SingleTon01 singleTon01 = null;
    private SingleTon01() { }

    public static SingleTon01 getSingleTon01() {
        if (singleTon01 == null) {
            synchronized (SingleTon01.class) {
                if (singleTon01 == null) {
                    singleTon01 = new SingleTon01();
                }
            }
        }
        return singleTon01;
    }
}

        - 双重检锁机制不一定线程安全,原因是存在指令重排
        - 线程1在初始化(执行singleTon01 = new SingleTon01())时,分为三步,分配的内存空间、创建对象、引用指向内存空间,但是后两步不存在依赖关系,即可能发生指令重排
        - 线程2调 getSingleTon01 方法判断第一个if时,发现引用指向分配内存空间(此时,singleTon01 != null,但是此时线程1仅仅是分配了内存空间 + 引用指向内存空间)
        - 线程2有概率 return null; 出现线程安全问题
    此时需要加上 volatile: private static volatile SingleTon01 singleTon01 = null; // 保证分配的内存空间、创建对象、引用指向内存空间按顺序执行,这样线程2进入第一个if,等线程1完成后,线程2到达第二个if,发现对象已实例化完成,此时返回线程1的实例化对象

    
3.以后单例 = 双重检锁机制 + volatile禁止指令重排
    
三、CAS及ABA问题
1.unsafe类 位于 rt.jar\sun\misc包下,是CAS的核心类,Unsafe类中的方法都是native方法,是为JAVA执行特定内存操作而提供操作底层系统资源方法集
2.AtomicInteger中,就是通过unsafe类获取对象的内存地址偏移量再实现比较和替换,volatile 保证了多线程之间内存可见性
3.CAS缺点:1)循环时间长,开销会很大;2)只能对单个共享变量进行原子操作;3)ABA问题
4.ABA问题的解决

public class AbaDemo {

    private static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);

    private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {
        // ABA问题 原型
//        new Thread(() -> {
//            System.out.println(Thread.currentThread().getName() + "\t begin");
//            atomicReference.compareAndSet(100, 101);
//            atomicReference.compareAndSet(101, 100);
//            System.out.println(Thread.currentThread().getName() + "\t end");
//        }, "t1").start();
//        new Thread(() -> {
//            System.out.println(Thread.currentThread().getName() + "\t begin");
//            // 先休息 保证 t1线程 完成ABA操作
//            try {
//                TimeUnit.SECONDS.sleep(1);
//                boolean res = atomicReference.compareAndSet(100, 101);
//                System.out.println("操作结果:" + res + ", 当前值:" + atomicReference.get());
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName() + "\t end");
//        }, "t2").start();

        System.out.println("================================");

        // ABA问题解决 使用AtomicStampedReference
        new Thread(() -> {
            int version1 = atomicStampedReference.getStamp();
            System.out.println("t3拿到的初始版本:" + version1);
            try {
                // 休息1秒,让t4拿到相同版本的100
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101, version1, version1 + 1);
            int version2 = atomicStampedReference.getStamp();
            atomicStampedReference.compareAndSet(101, 100, version2, version2 + 1);
        }, "t3").start();
        new Thread(() -> {
            // 先休息 保证 t1线程 完成ABA操作
            try {
                int version = atomicStampedReference.getStamp();
                System.out.println("t4拿到的初始版本:" + version);
                // 休息3秒,让T3完成ABA操作,t4再尝试getAndSet
                TimeUnit.SECONDS.sleep(3);
                boolean res = atomicStampedReference.compareAndSet(100, 101, version, version + 1);
                System.out.println("t4操作结果:" + res + ", 当前值:" + atomicStampedReference.getReference() + ", 当前版本:" + atomicStampedReference.getStamp());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t4").start();
    }

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值