Java多线程 CAS与原子类Atomic

什么是原子类

  • 不可分割
  • 一个操作是不可中断的

作用和锁类似,是为了保证并发情况下的线程安全,不过原子类相比于锁,有一定的优势。

  • 粒度更细:原子变量可以把竞争范围缩小到变量级别,这是我们可以获得最细粒度的情况了,通常锁的粒度都要大于原子变量的粒度
  • 效率更高,通常,使用原子类的效率比使用锁的效率更高。

6类原子类总览

  • 基本类型原子类
    • AtomicInteger
    • AtomicLong
    • AtomicBoolean
  • 数组类型原子类
    • AtomicIntegerArray
    • AtomicLongArray
    • AtomicReferenceArray
  • 引用类型原子类
    • AtomicReference
    • AtomicStampedReference
    • AtomicMarkableReference
  • 升级类型原子类
    • AtomicIntegerfieldupdater
    • AtomicLongFieldUpdater
    • AtomicReferenceFieldUpdater
  • Adder累加器
    • LongAdder
    • DoubleAdder
  • Accumulator累加器
    • LongAccumulator
    • DoubleAccumulator

Atomic基本类型原子类

常用方法

  • get()获取当前的值
  • getAndSet(int newValue)获取当前的值,并设置新值
  • getAndIncrement()获取当前的值,并自增
  • getAndDecrement()获取当前的值,并自减
  • getAndAdd(int delta)获取当前的值并加上预期的值
  • compareAndSet(int expect,int update)如果当前的数值等于预期值,则以原子方式将该值设置为输入值

AtomicInteger

public class AtomicIntegerDemo1 implements Runnable{
    private static final AtomicInteger atomicInteger = new AtomicInteger();
    public void incrementAtomic(){
        atomicInteger.getAndIncrement();
    }
    private static volatile int basicCount = 0;
    public void incrementBasic(){
        basicCount++;
    }
    public static void main(String[] args) {
        AtomicIntegerDemo1 r = new AtomicIntegerDemo1();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(atomicInteger);
        System.out.println(basicCount);
    }
    @Override
    public void run() {
        for(int i = 0;i < 10000;i++){
            incrementBasic();
            incrementAtomic();
        }
    }
}

AtomicIntegerfieldupdater

public class AtomicIntegerFieldUpdaterDemo implements Runnable{
    static Candidate tom;
    static Candidate peter;
    public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater
            = AtomicIntegerFieldUpdater.newUpdater(Candidate.class,"score");
    @Override
    public void run() {
        for(int i = 0; i < 10000; i++){
            peter.score++;
            scoreUpdater.getAndIncrement(tom);
        }
    }
    public static void main(String[] args) {
        tom = new Candidate();
        peter = new Candidate();
        AtomicIntegerFieldUpdaterDemo r = new AtomicIntegerFieldUpdaterDemo();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(tom.score);
        System.out.println(peter.score);
    }
}

累加器Adder

在这里插入图片描述

高并发下LongAdder与AtomicLong效率高,不过本质是空间换时间,竞争激烈的时候,LongAdder把不同线程对应到不同的Cell上进行修改,降低了冲突的概率,是多段锁的理念,提高了并发性。

public class LongAdderDemo {
    public static void main(String[] args) {
        LongAdder counter = new LongAdder();
        ExecutorService service = Executors.newFixedThreadPool(20);
        for(int i = 0; i < 10000; i++){
            service.submit(new Task(counter));
        }
        service.shutdown();
        while(!service.isTerminated()){
        }
        System.out.println(counter.sum());
    }
    private static class Task implements Runnable{
        private LongAdder counter;
        public Task(LongAdder counter){
            this.counter = counter;
        }
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                counter.increment();
            }
        }
    }
}

AtomicLong的性能远远不如LongAdder。AtomicLong每一次假发都要flush和refresh,导致很耗费时间。LongAdder不需要保持线程之间的同步,不会通过内存做中转。再内部,这个LongAdder的实现原理和刚才的AtomicLong是由不同的,刚才的AtomicLong的实现原理和刚才的AtomicLong是有不同的,刚才的AtomicLong的实现原理是,每一次假发都需要做同步,所以再高并发的时候会导致冲突比较多,也就降低了效率。而LongAdder,每个线程会有自己的一个计数器,仅用来再自己线程内技术,这样一来就不会和其他线程的计数器造成干扰。
LongAdder引入了分段累加的概念,内部有一个base变量和一个Cell[]数组共同参与计数:

  • base变量:竞争不激烈,直接累加到该变量上
  • Cell[]数组:竞争激烈,各个线程分散累加到自己的槽Cell [i]中

sum源码

还没看

CAS原理

  1. 什么是CAS
  2. 案例演示
  3. 应用场景

什么是CAS(CompairAndSwap)

用于并发,
我认为V的值应该是A,如果是的话那我就把它改成B,如果不是A(说明被别人修改过了),那么我就不修改了,避免多人同时修改导致出错。
CAS有三个操作数:内存值V、预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才将内存值修改为B,否则什么都不做。最后返回现在的V值。

CAS的等价代码

public class SimulatedCAS {
    private volatile int value;
    public synchronized int compareAndSwap(int expectedValue,int newValue){
        int oldValue = value;
        if(oldValue == expectedValue){
            value = newValue;
        }
        return oldValue;
    }
}

应用场景

  • 乐观锁
  • 并发容器
  • 原子类

分析Java中如何利用CAS实现原子操作

  • AtomicInteger加载Unsafe工具,用来直接操作内存数据
  • 用Unsafe来实现底层操作
  • 用volatile修饰value字段,保证可见性
  • getAndAddInt方法分析

原子类中的getAndAdd方法的实现

public final int getAndAdd(int delta){
    return unsafe.getAndAddInt(this,valueOffset,delta);
}

这里使用Unsafe这个类,这里简要介绍一下Unsafe

Unsafe是CAS的核心类,Java无法直接访问底层操作系统,而是通过本地native方法来访问。不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的的原子操作。
AtomicInteger加载Unsafe工具,用来直接操作内存数据

public class AtomicInteger extends Number implements java.io.Serializable{
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    static {
        try{
            valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
        }catch(Exception ex){throw new Error(ex);}
    }
    private volatile int value;
    public final int get(){return value;}
}

再AtomicInteger数据定义的部分,我们还获取了unsafe实例,并且定义了valueOffset。在看到static块,由类加载过程我们可以直到static块加载发生于类加载的时候,是最先初始化的,这时候我们调用unsafe的objectFieldOffset从Atomic类文件中获取value的偏移量,那么valueOffset其实就是记录value的偏移量的。valueOffset表示的是变量值再内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的原值的,这样我们就能通过unsafe来实现CAS了,value是用volatile修饰的,保证了多线程之间看到value值是同一份。下面时unsafe类的getAndAddInt方法实现。

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;
}

var5获取的就是通过调用unsafe的getIntVolatile(var1,var2),这是个native方法,其实就是获取var1中,var2偏移量处的值。var1就是AtomicInteger,var2就是我们前面提到的valueOffset,这样我们就可以从内存里获取现在valueOffset处的值了。

CAS的缺点

  • ABA问题 添加版本号来解决。
  • 并发竞争太大时,自旋时间过长。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值