2.解析自旋锁CAS操作与volatile

volitile

volatile保证线程的可见性,同时防止指令的重排序,
线程可见性在CPU级别使用缓存一致性来保证的,
禁止指令重排序是CPU级别,是你禁止不了的,是内部运行的过程,提高效率的,
但是在加了volatile之后,这个指令重排序就可以禁止了,
严格来说,他是加了读屏障和写屏障,是CPU的一个源语。
1.保证线程可见性:
如果没有volatile修饰,那么线程不会终止.
每个线程都是在自己的运行空间执行操作,何时将flag写会公共区域不可知,加上volatila修饰flag,保证可见性
public class T1 {
    volatile boolean run = true;
    public void m1(){
        System.out.println("m1 start ...");
        while (run){
            //System.out.println("m1 runing ...");
        }
        System.out.println("m1 stop ...");
    }
    public static void main(String[] args) throws InterruptedException {
        T1 t = new T1();
        new Thread(t::m1,"t1").start();
        Thread.sleep(1000);
        t.run = false;
    }
}
输出:
m1 start ...
m1 stop ...
2.禁止指令重排序:
指令重排序:和CPU有关系,CPU会并行执行多条执行,加了volatile,编译的时候,将不会进行重排序
  • DCL单例
  • 饿汉式
    //饿汉式 先初始化好对象,调用instance方法的时候进行返回
    public class Mgr01 {
    private static final Mgr01 m = new Mgr01();//私有方法产生对象
    private Mgr01(){}//私有构造方法禁止其他类new对象
    public static Mgr01 getInstance(){
        return m;//共有方法产生对象
    }
    public static void main(String[] args) {
        Mgr01 m1 = Mgr01.getInstance();
        Mgr01 m2 = Mgr01.getInstance();
        System.out.println(m1 == m1);
    }
}
  • 懒汉式

    多线程不安全

public class Mgr02 {
    private static Mgr02 mgr02;
    private Mgr02(){}
    //多线程调用会产生多个对象
    public static Mgr02 getInstance(){
        if(mgr02 == null){
            System.out.println("mgr02 is null");
            mgr02 = new Mgr02();
        }
        return mgr02;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->
                    System.out.println(Mgr02.getInstance().hashCode())
                    ).start();
        }
    }
}
输出:
mgr02 is null
mgr02 is null
1117110646
mgr02 is null
122651855
mgr02 is null
1343599390
mgr02 is null
588558273
mgr02 is null
1926683415
588558273
588558273
1343599390
  • 懒汉式 双重检查
public class Mgr03 {
    private static /*volatile*/ Mgr03 INSTANCE;
    private Mgr03(){}

    public static Mgr03 getInstance(){
        if(INSTANCE == null){
            System.out.println(Thread.currentThread().getName()+" is null");
            synchronized (Mgr03.class){
                System.out.println(Thread.currentThread().getName()+" get lock!");
                if(INSTANCE == null){
                    System.out.println(Thread.currentThread().getName()+" get Object!");
                    INSTANCE = new Mgr03();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->
                    System.out.println(Mgr03.getInstance().hashCode())
            ,"Thread :"+ i).start();
        }
    }
}
输出:
Thread :0 is null
Thread :5 is null
Thread :6 is null
Thread :7 is null
Thread :0 get lock!
Thread :3 is null
Thread :4 is null
Thread :0 get Object!
1926683415
Thread :8 is null
1926683415
Thread :9 get lock!
1926683415
  • 懒汉式 双重检查,要不要加volatile?

    • 要加volatile,原因是在指令重排序上,JVM创建对象有三部:申请内存,初始化,赋值对象
    • 指令重排序有时候会将赋值和初始化调换位置
    • 在高并发情况下,会拿到还没有被赋值的对象,而不是赋值之后的对象
    • 加了volatile之后,不会发生指令重排序,保证多线程获取到对象一定是赋值之后的对象

对象赋值三部:

在这里插入图片描述

  • volatile 只能保证可见性,不能保证synchronized原子性
  • Doulbe Check Lock
  • 读写屏障

###CAS(无锁优化 、乐观锁)

Atomic开头的都是CAS操作
1.Atomic
public class Atomic01 {
    AtomicInteger atomicInteger = new AtomicInteger(0);
    void m1(){
        for (int i = 0; i < 10000; i++) {
            atomicInteger.incrementAndGet();//自增
        }
    }

    public static void main(String[] args) {
        Atomic01 atomic01 = new Atomic01();
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(atomic01::m1,"thread - " +i));
        }
        threads.forEach((o) ->o.start());//线程启动
        threads.forEach((o) -> {
            try {
                o.join();//线程顺序执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(atomic01.atomicInteger);
    }
}
输出:
Connected to the target VM, address: '127.0.0.1:52118', transport: 'socket'
100000
Disconnected from the target VM, address: '127.0.0.1:52118', transport: 'socket'
原理
  • CAS(当前值V,期望值E,新值N)
如果V=E,则设置N,否则设置失败,重新来一次
这是CPU原语,是不能被打算的
//unsafe类
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
//
public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
//
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }
ABA问题
使用CAS会产生问题:
原值是A,改成了B,又改成了A
如果是基础类型:无所谓,不影响结果
如果是引用类型:中间经历了别人
解决方法:加version版本,每次做CAS的时候,检查版本号
Unsafe类
  • 单例
    //私有构造器
    private Unsafe() {
    }

    //获取unsafe对象
    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
所有的CAS底层都是调用Unsafe类的compareAndSwap方法,直接操作操作系统
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值