Java并发之CAS

在并发的世界里,有两种心态--乐观和悲观。乐观的人总认为这事不一定发生,侥幸地先试一把再说。悲观的人认为,哦,这件事可能会有这样那样的负面,不能轻易尝试。这些对应到我们java的并发策略就加锁和无锁。
啥?并发还有无锁这种操作?真有,而且效率相当地高。为什么效率高呢?因为加锁,意味着就有获取锁,释放锁,等待锁,阻塞。。这些,会带来线程的切换,只要线程一切换,就会要保存现场,这样的操作都是代价昂贵的,我们不希望这样的情况发生(或者尽量避免)。
本文就分析在无锁情况下,如何保证并发的安全。答案就是cas.
先看一个demo。

public class AtomicIntegerDemo {
        static AtomicInteger i = new AtomicInteger();
        public static class AddThread implements Runnable{
            @Override
            public void run() {
                for (int j = 0; j < 10000; j++) {
                    i.incrementAndGet();
                }
            }
        }
        public static void main(String[]s) throws InterruptedException{
                Thread[] t= new Thread[10];
            for (int j = 0; j < 10; j++) {
                t[j]= new Thread(new AddThread());
            }
            for (int j = 0; j < 10; j++) {
                t[j].start();
            }
            for (int j = 0; j < 10; j++) {
                t[j].join();
            }
            System.out.println(i);//结果应该是10000*10
        }
}
复制代码

这是为啥呢?一般有10个线程去执行一个i++肯定总数会小于10000*10的。 带着这个好奇我们先说下cas的铺垫,再看源码。
cas的基本思想如下:
cas(当前值, 期望值,目标值)。当执行这个cas()这个方法时,方法内部会判断当前值是否等于期望值,若等,就把当前值设为目标值,返回true。不等,返回false(说明有别的线程更改了当前值)。那咋办呢?此时一般会有个for(;;)死循环,重试,直到返回ture。为什么可以重试,上面说了这里线程没锁,当前线程有了执行权,只要没被cpu调度,就一直拥有执行权,就一直再执行for循环。现在cpu已经保证了cas这个比较并交换指令是原子性的了。。也就是说这个cas()方法嗖的一下就执行完了,不会被打断。
现在看下incrementAndGet()的实现。

public final int incrementAndGet(){
    for(;;){
        int current = get();//返回当前值
        int next = current+1;
        if(compareAndSet(current,next)){
            return next;
        }
    }
}
复制代码

啊,手欠,还是想看看compareAndSet(current,next)的实现,不然总感觉不踏实,对就是这种感觉,不知道读这篇文章的你,有没这种感觉。

public final boolean compareAndSet(int expect ,int update){
return unsafe.compareAndSwapInt(this,valueOffset,expext,update);
}
复制代码

这里出现一个我们不熟悉的变量unsafe,它是sun.misc.Unsafe类型。我猜测这个类应该是封装了些不安全的操作。啥是不安全的呢?指针!因为指针如果指错了位置,就可能覆盖了别人的内存,导致系统崩溃。Java中屏蔽了这一玩意,当有时还必须请出这玩意,这里就是一个例子。
继续进入unsafe.compareAndSwapInt()
public final native boolean compareAndSwapInt(Object o, long offset, int expect, int x); 这个方法是native的,我们应用层面是调不到的。这个方法内部就是使用了上面的cas()思想,原子指令完成。
上面的unsafe是通过这个方法获得的

   public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if((var0.getClassLoader() !=null) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
复制代码

上面的代码会检查调用getUnsafe()的类的classLoader是否为null。这就使得我们的应用程序无法使用Unsafe类。
补充一下类加载器知识。
根据Java类加载原理,应用程序由AppLoader加载。而系统核型类由Bootstrap加载。Bootstrap加载器没java对象的对象,因此获得这个类的加载器为null,所以,当一个类的加载器为null时,说明它是由Bootstrap加载的。

猜你喜欢

转载自juejin.im/post/5b1be099e51d4506a521a199