Web全栈~34.CAS

Web全栈~34.CAS

上一期

原子变量

Java并发包中的原子变量有以下几种

       AtomicBoolean:原子Boolean类型,常用来在程序中表示一个标志位。

       AtomicInteger:原子Integer类型。

       AtomicLong:原子Long类型,常用来在程序中生成唯一序列号。

       AtomicReference:原子引用类型,用来以原子方式更新复杂类型。

       除了这4个类,还有一些其他类,如针对数组类型的类AtomicLongArray、AtomicReferenceArray,以及用于以原子方式更新对象中的字段的类,如AtomicIntegerFieldUpdater、AtomicReferenceFieldUpdater等。

       Java 8增加了几个类,在高并发统计汇总的场景中更为适合,包括LongAdder、LongAccumulator、Double-Adder和DoubleAccumulator

AtomicInteger

       AtomicInteger有两个构造方法,第一个给定初始值,第二个初始值为0

Web全栈~34.CAS_第1张图片

       可以直接获取或设置AtomicInteger中的值,方法是

Web全栈~34.CAS_第2张图片

       之所以称为原子变量,是因为它包含一些以原子方式实现组合操作的方法

//以原子方式获取旧值并设置新值
public final int getAndSet(int newValue)
//以原子方式获取旧值并给当前值加1
public final int getAndIncrement()
//以原子方式获取旧值并给当前值减1
public final int getAndDecrement()
//以原子方式获取旧值并给当前值加delta
public final int getAndAdd(int delta)
//以原子方式给当前值加1并获取新值
public final int incrementAndGet()
//以原子方式给当前值减1并获取新值
public final int decrementAndGet()
//以原子方式给当前值加delta并获取新值
public final int addAndGet(int delta)

       这些方法的实现都依赖另一个public方法:

public final boolean compareAndSet(int expece,int update)

       compareAndSet是一个非常重要的方法,比较并设置,简称为CAS。

       该方法有两个参数expect和update,以原子方式实现了如下功能:如果当前值等于expect,则更新为update,否则不更新,如果更新成功,返回true,否则返回false。AtomicInteger可以在程序中用作一个计数器,多个线程并发更新,也总能实现正确性。

public class Test extends Thread{
     
    private static AtomicInteger counter = new AtomicInteger(0);
    static class Visitor extends Thread {
     
        @Override
        public void run() {
     
            for(int i = 0; i < 1000; i++) {
     
                counter.incrementAndGet();
            }
        }
    }
    public static void main(String[]args) throws InterruptedException {
     
        int num = 1000;
        Thread[]threads = new Thread[num];
        for(int i = 0; i < num; i++) {
     
            threads[i]= new Visitor();
            threads[i].start();
        }
        for(int i = 0; i < num; i++) {
     
            threads[i].join();
        }
        System.out.println(counter.get());
    }
}

基本原理和思维

       AtomicInteger的使用方法是简单直接的,它的主要内部成员是 :

//它的声带有volatile,这是必须的,以保证内存可见性
private volatile int value;

       它的大部分更新方法实现都类似

public final int incrementAndGet() {
     
	for(;;) {
     
		int current = get();
		int next = current + 1;
		ifcompareAndSet(current,next))
		return next;
	}
}

       代码主体是个死循环,先获取当前值current,计算期望的值next,然后调用CAS方法进行更新,如果更新没有成功,说明value被别的线程改了,则再去取最新值并尝试更新直到成功为止。

       与synchronized锁相比,这种原子更新方式代表一种不同的思维方式。synchronized是悲观的,它假定更新很可能冲突,所以先获取锁,得到锁后才更新。原子变量的更新逻辑是乐观的,它假定冲突比较少,但使用CAS更新,也就是进行冲突检测,如果确实冲突了,那也没关系,继续尝试就好了。synchronized代表一种阻塞式算法,得不到锁的时候,进入锁等待队列,等待其他线程唤醒,有上下文切换开销。原子变量的更新逻辑是非阻塞式的,更新冲突的时候,它就重试,不会阻塞,不会有上下文切换开销。对于大部分比较简单的操作,无论是在低并发还是高并发情况下,这种乐观非阻塞方式的性能都远高于悲观阻塞式方式。

       原子变量相对比较简单,但对于复杂一些的数据结构和算法,非阻塞方式往往难于实现和理解,幸运的是,Java并发包中已经提供了一些非阻塞容器,我们只需要会使用就可以了

       除了可以实现乐观非阻塞算法之外,还可以实现悲观阻塞式算法,比如锁。实际上,Java并发包中的所有阻塞式工具、容器、算法也都是基于CAS的

public class Test extends Thread{
     
    private AtomicInteger status = new AtomicInteger(0);
    public void lock() {
     
        while(!status.compareAndSet(0,1)) {
     
            Thread.yield();
        }
    }
    public void unlock() {
     
        status.compareAndSet(1,0);
    }
}

       使用status表示锁的状态,0表示未锁定,1表示锁定,lock()、unlock()使用CAS方法更新,lock()只有在更新成功后才退出,实现了阻塞的效果,不过一般而言,这种阻塞方式过于消耗CPU

你可能感兴趣的:(web,并发编程)