JUC之CAS

CAS(全称为CompareAndSwap,也有说是CompareAndSet,都差很少)是一条CPU并发原语,它的功能是判断内存某个位置的值是否为预期值,若是是则更改成新的值,判断预期值和更改新值的整个过程是原子的。在JAVA中,CAS的实现所有在sun.misc.Unsafe类中的各个方法,调用UnSafe类中的CAS方法,JVM会帮咱们实现出CAS汇编指令,这是一种彻底依赖于硬件的功能。java

在传统方式中实现并发的手段是加锁,JAVA中的锁有synchronized和Lock(jdk1.5才有)。Lock是基于AQS和CAS实现的,这里先跳过。对于synchronized锁,JVM在执行它的时候会依赖操做系统的临界区机制。这样的话,每次执行到synchronized锁,都会经历用户态和内核态之间的切换。这个过程的消耗是很大的。并且,大多数时候synchronized锁住的操做是很细粒度的。为了细粒度的操做去经历用户态和内核态之间的切换是低效的作法。并发

其实最多见的就是咱们须要并发修改某个变量值,举个常见的例子,窗口售票,不加锁的代码以下所示:ide

public class Test {

    public static void main(String[] args) {
        Stock stock = new Stock(10);
        for (int i = 0; i < 5; i++) {
            new Thread(stock, "窗口" + (++i)).start();
        }
    }

    private static class Stock implements Runnable {
        private volatile int count;

        public Stock(int count) {
            this.count = count;
        }

        @Override
        public void run() {
            for (;;) {
                count--;
                if (count < 0) {
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "售出一张票,剩余票数:" + count);
                try {
                    Thread.sleep(100L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

 

获得的结果:
(票数和线程太少,须要多跑几遍,多了几乎一遍就能够看出问题)性能

获得的结果很明显,一共卖出了12张票,其中红框中出现的数字就能够说明问题。出现问题的缘由很简单,由于--count这个算式表达式并非原子的。在一个线程对count进行计算赋值后,但尚未将新值推送到内存中时,另外一个线程获取的count值仍是原来的值,当这个线程拿着这个值去进行计算,就会出现上面的问题。(这个涉及到Java的内存模型JMM,有兴趣的能够自行了解)
在JDK1.5以前,咱们想要解决这个问题,就只能使用synchronized进行加锁,以下:优化

public void run() {
for (;;) {
synchronized (this) {
count--;
}
if (count < 0) {
return;
}
System.out.println(Thread.currentThread().getName() + "售出一张票,剩余票数:" + count);
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

可是若是仅仅是相似于--count这种并发计数功能,须要进行同步的操做粒度很细时,使用synchronized就大材小用了,不高效(即使如今synchronized通过不少优化,再也不想最初那样耗资源,可是它毕竟是个锁,并且多个线程进行竞争的时候仍是会变成重量级锁),而使用CAS来实现就会更加的轻量级,性能更好。先上代码再说this

public class Test {

    private static Unsafe unsafe;

    public static void main(String[] args) throws Exception {
        Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafeField.setAccessible(true);
        unsafe = (Unsafe) theUnsafeField.get(null);
        Stock stock = new Stock(10);
        for (int i = 0; i < 5; i++) {
            new Thread(stock, "窗口" + (++i)).start();
        }
    }

    private static class Stock implements Runnable {
        private volatile int count;
        private static long countOffset;
        public Stock(int count) {
            this.count = count;
            try {
                countOffset = unsafe.objectFieldOffset(this.getClass().getDeclaredField("count"));
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            for (;;) {
                int x = this.count;
                int y = x - 1;
                if (!unsafe.compareAndSwapInt(this, countOffset, x, y)) {
                    continue;
                }
                if (y < 0) {
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "售出一张票,剩余票数:" + y);
                try {
                    Thread.sleep(100L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

像上面代码同样,当咱们使用CAS进行操做时,出现的效果和使用synchronized同样的。代码中使用了JAVA提供的sun.misc.Unsafe进行CAS操做,并且在代码总我使用反射进行获取Unsafe实例,之因此这样作,是由于JDK不想让咱们开发者去直接使用Unsafe这个类,并且使用起来比较繁琐,他们给咱们提供了一些封装好的类来供咱们开发者使用,好比经常使用的java.util.concurrent.atomic.AtomicInteger、java.util.concurrent.atomic.AtomicBoolean、java.util.concurrent.atomic.AtomicIntegerArray。这些类中都有相同的特色,就是使用sun.misc.Unsafe进行CAS操做,内部进行了一些相似上面代码的封装,咱们就以AtomicInteger进行代码演示。atom

public class Test {

    public static void main(String[] args) throws Exception {
        Stock stock = new Stock(10);
        for (int i = 0; i < 5; i++) {
            new Thread(stock, "窗口" + (++i)).start();
        }
    }

    private static class Stock implements Runnable {
        private volatile AtomicInteger count;
        public Stock(int count) {
            this.count = new AtomicInteger(count);
        }

        @Override
        public void run() {
            for (;;) {
                int x = count.get();
                int y = x - 1;
                if (!count.compareAndSet(x, y)) {
                    continue;
                }
                if (y < 0) {
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "售出一张票,剩余票数:" + y);
                try {
                    Thread.sleep(100L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

就像上面的代码同样,咱们没必要进行反射获取Unsafe,而后在获取字段在class中的偏移量这些繁琐的操做了,下面咱们就去看看AtomicInteger的源码 spa

  

看上面AtomicInteger的源码咱们能够很明显的看的出来,在AtomicInteger进行类加载的时候,会经过sun.misc.Unsafe获取value这个变量在类文件中的偏移量,进行保存,跟咱们直接使用Unsafe的操做是同样的。咱们找到刚才使用的compareAndSet(x, y)方法的源码,能够看到底层就是使用unsafe实例进行CAS操做。操作系统

AtomicInteger还有一些别的方法,好比getAndIncrement、getAndDecrement、getAndAdd、incrementAndGet、decrementAndGet等等,底层实际上仍是使用的unsafe实例进行CAS操做,有兴趣的同窗能够本身翻下源码看看,这里就很少说了。线程

总结:CAS的出现就是为了解决一些简单的并发操做,将比较、赋值做为一个原子操做记性处理,实现无锁化处理,节省资源开销。

相关文章
相关标签/搜索