非阻塞算法-简单的计数器

1.为何要用非阻塞算法?
咱们知道为了不并发环境下操做共享变量的问题,能够采用同步(synchronize)和锁(Lock)的方式作到线程安全,可是JVM处理锁竞争时对于竞争失败的线程采用的是挂起稍后调度的策略,这样会带来额外的线程上下文切换成本。同时和CAS(Compare And Set)这种非阻塞算法相比,CAS是在底层硬件(CPU)层面实现,只须要锁定独立的内存位置,更细的同步粒度使得CAS失败的线程能够当即重试而不用挂起。总的来讲,大多数场景下非阻塞算法和同步锁相比能带来更好的性能,最小化串行代码的粒度是并行性能的关键。java

2.非阻塞算法的弊端
CAS算法是先compare再set,若是compare结果为false的话就不会执行set直接返回false,也就是说非阻塞算法可能会失败,所以非阻塞算法每每须要放在循环里不断重试直到成功,因此在锁竞争很是严重时非阻塞算法的性能可能会严重降低,甚至不如阻塞算法。算法

3.最简单的CAS用法
CAS的全称是Compare and Set,是现代主流CPU都支持的一个CPU原子语义操做,简单来讲就是先跟某个值对比,若是等于这个值就设置成新的值,若是不等于这个值就放弃操做同时返回失败。这里咱们看一下最经常使用的计数器,首先咱们看一个单线程的计数器:安全

public class SingleThreadCounter implements Counter {

    @Override
    public void increment() {
        this.counter++;
    }

    @Override
    public int getCounter() {
        return counter;
    }

    private int counter = 0;
}

这个计数器在单线程下彻底没有问题,可是一旦有多个线程并发执行increment,就会出现问题,由于counter++ 是先取值,再赋值,若是A线程取出1,B线程也取出1,A设置counter为2,B后设置counter也为2,最终B把A的结果覆盖了。数据结构

接下来咱们看同步的计数器多线程

public class SynchronizedCounter implements Counter {

    private volatile int counter = 0;

    public synchronized void increment() {
        counter = counter + 1;
    }


    @Override
    public int getCounter() {
        return counter;
    }
}

这个计数器解决了前面一个多线程并发increment的问题,由于increment方法是同步的,不会有两个线程同时执行increment操做,实际上increment操做是串行的。这种方式保证了线程安全,可是牺牲了并发度。并发

最后咱们看无锁算法的计数器ide

public class CasCounter implements Counter {

    private AtomicInteger counter = new AtomicInteger(0);

    @Override
    public void increment() {
        for (; ; ) {
            int cur = counter.get();
            int next = cur + 1;
            if (counter.compareAndSet(cur, next)) {
                return;
            }
        }
    }

    @Override
    public int getCounter() {
        return counter.get();
    }
}

咱们看这个类里的increment方法,其实这个方法和AtomicInteger里的incrementAndGet方法是同样的,就是在每一个循环里作一次CAS操做,先取出当前拿到的值cur,而后CAS(cur,cur+1),若是返回成功的话说明更新成功了,那就直接return;若是失败的话说明其余线程已经改变了counter的值,就继续循环执行前面的操做直到成功为止。这里须要注意的是counter.get方法返回的底层变量是一个volatile的值,能够保证内存的可见性,所以不会出现读到脏值的问题。性能

4.总结
CAS算法和同步锁相比,把串行的粒度从方法级别下降到CPU的CAS原子操做,提升了并发度,所以提升了系统的容量,可是CAS在失败时是须要重试的,所以若是并发度很高,竞争十分严重的状况下,由于须要大量重试操做,CAS的效率可能甚至不如同步方法。
下一节咱们继续讲如何把CAS算法用在一些更复杂一些的数据结构上。this

相关文章
相关标签/搜索