Atomic原理+问题

Atomic原理+问题

问题:

  1. 什么是Atomic?

    Atomic包下的原子类,是不可中断的一系列操作,保证了多线程下的安全性。

    原子类是使用volatile和cas操作来保证原子性的,并不是锁

  2. 原子类有哪些?

    1.基本类型的原子类(3)
    	AtomicInteger、AtomicLong、AtomicBoolean
    2.数组类型原子类(3)
    	AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray<E>
    3.引用类型(1)
    	AtomicReference
    4.升级普通变量原子类(3)---不能升级被private、static修饰的普通变量
    	AtomicIntegerFiledUpdater<T>、AtomicLongFiledUpdater、AtomicReferenceFiledUpdater
    5.Adder累加器(2)
    	LongAdder、DoubleAdder
    6.Accumulator(2)
    	LongAccumulator、DoubleAccumulator
    	
    AtomicStampedReference<V> AtomicMarkableReference<V> 解决ABA问题
    
  3. 什么是CAS方法?

    比较并交换
    cas(56,57) 预期内存值是56 当前内存值为56 要修改为57  预期值与内存值比较,相同则修改为修改值 不同返回预期值56
    CAS缺点 ABA问题 自旋时间过长 
    原子类现在直接掉本地方法去了
    要想看自旋也可以看看这个方法
    
    //原子更新,改不成成功就一直自旋
    public final int getAndUpdate(IntUnaryOperator updateFunction) {
        int prev, next;
        do {
            prev = get();
            next = updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev, next));
        return prev;
    }
    

    ABA问题:

     cas只是比较预期值和内存值是否相同,可能A线程在比较,但是期间b线程将a值改为了b,c线程将b值改成了a 对于a线程来讲值并未被修改。
    

AtomicInteger为什么是原子类?

查看源代码可以看到:

private volatile int value;

AtomicInteger类的值被volatile修饰,这个volatile可以保证value值一被修改则对使用线程可见,这就保证了所有线程读的时候的可见性

比较常用的是它的自增自减

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
private static final Unsafe unsafe = Unsafe.getUnsafe();// Unsafe封装了原子操作硬件的方法
//java并不能直接操作硬件
//Unsafe类中的方法,可见调用了cas方法
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);//通过当前对象var1和对象在内存的的偏移量 确定当前value值
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//如果比较旧value相等,那么就让旧value+1并替换旧vlaue

    return var5;//返回的是旧value
}
//native说明此方法是个本地方法,就是调用本地的c++方法去cas修改值
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

第一类是原子更新基本类型类,就三种基本方法都一样。

但是这里只提供了三种基本类型,要知道Java基本类型有8种,那么其它的怎么办?

1.Unsafe类提供的只有三个CAS方法
	 public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
  也并无boolean型的啊
  
2.查看AtomicBoolean的源代码发现先把Boolean转换为整型然后使用compareAndSwapInt进行CAS的,
  所以其它类型我们也可以这样操作
 public final boolean compareAndSet(boolean expect, boolean update) {
        int e = expect ? 1 : 0;
        int u = update ? 1 : 0;
        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }

原子更新数组

只看了AtomicIntegerArray,其它的和这个方法差不多

主要使用的方法就是原子更新数组中int形数据

  1. //delta+数组(i) 原子更新 返回更新后的值
    public final int addAndGet(int i, int delta) {
        return getAndAdd(i, delta) + delta;
    }
    //和原子更新AtomicInteger一样的方法
    public final int getAndAdd(int i, int delta) {
            return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
    }
    
  2. //将数组传入的构造参数,会拷贝一份array,所以对数组的更改不会影响到原数组
    public AtomicIntegerArray(int[] array) {
        // Visibility guaranteed by final field guarantees
        this.array = array.clone();
    }
    

原子更新引用类型

​ 原子的更新引用对象

​ 常用方法

//先比较是期望的expect值,是则更新为update
public final boolean compareAndSet(V expect, V update) {
    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

AtomicMarkableReference,原子更新带有标记位的引用类型----CAS中比较旧值的同时比较标记位,如果旧值比较通过,标记位比较不通过还是不会去set
查看源码,怎么做的呢?

//首先看到一个被volatile变量修饰的内部类,这个内部类封装了当前呆更新对象和标记位,修改这些变量可以对所以线程可见(volatile特性)
private volatile Pair<V> pair;
private static class Pair<T> {
        final T reference;
        final boolean mark;
        private Pair(T reference, boolean mark) {
            this.reference = reference;
            this.mark = mark;
        }
        static <T> Pair<T> of(T reference, boolean mark) {
            return new Pair<T>(reference, mark);
        }
    }

只有这一个构造方法

public AtomicMarkableReference(V initialRef, boolean initialMark) {
    pair = Pair.of(initialRef, initialMark);
}

CAS方法

//expectedReference == current.reference cas比较
//expectedMark == current.mark 比较标记位
//如果新引用和当前引用==,且新标记位和旧标记位一致,那么就不执行cas
public boolean compareAndSet(V       expectedReference,
                             V       newReference,
                             boolean expectedMark,
                             boolean newMark) {
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        expectedMark == current.mark &&
        ((newReference == current.reference &&  
          newMark == current.mark) || //这里如果前面()里面的都为true,那么就不会执行后面的cas操作,||特性
         casPair(current, Pair.of(newReference, newMark)));
}

casPair方法

private static final long pairOffset =
        objectFieldOffset(UNSAFE, "pair", AtomicMarkableReference.class);//通过对象的pair字段获取在内存的偏移量
private boolean casPair(Pair<V> cmp, Pair<V> val) {
        return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    }

原子更新字段类/升级普通变量原子类

原子的更新某个类里的某个字段

书上是/前的描述,视频课里面是后面描述,感觉后面更清晰

我买的 2020 3月那版书就这节有明显的失误,Java并发编程的艺术

注意点:该普通变量不可被private修饰,不能是static
不看了和之前的差不多

重要的是一个

AtomicStampedReference,这个也是解决ABA问题的,只不过是以版本号的形式,就是用一个int值,作为版本号

和AtomicMarkableReference 相似,就是一个以boolean判断,一个int值

Adder 累加器 空间换时间 效率高

  1. 为什么要有这个?

    因为AtomicLong累加效率差,是volatile修饰所以每次修改值都会通知所有线程,而线程每次读则都会去共享内存重新获取值

  2. LongAdder则是通过空间换取时间

    LongAdder每个线程内都会有自己的计数器,独立的就不需要去和其它线程同步

    引入了分段累加的概念

     1. 当竞争激烈的时候,会使用一个Cell[]数组,每个线程持有一个cell[i],先加到这里面
     2. 竞争不激烈的时候,使用一个base变量,和AtomicLong一致 transient volatile long base;
    
  3. 分析源码

    返回总和的源码,不能十分精确,可能遍历到中间前面的cell又有了变化

    public long sum() {
        Cell[] as = cells; Cell a;
        long sum = base;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }
    

Accumulator

更通用的Adder

提供了函数式接口,可以使用lambda

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值