【J.U.C-Atomic】Atomic原子类与实现原理

如果不使用原子类,要同步修改Integer等变量就需要加锁,atomic原子类就是解决这种问题。

其原理概括来说就是volatile关键字/final关键字+循环CAS尝试。
可以说是一种乐观锁的理念。

JUC介绍

JUC,即 java.util.concurrent 包的缩写,是java原生的并发包和一些常用的工具类。
它主要包括了以下几个功能:

  • atomic原子类
  • collections集合
  • lock锁
  • executor执行器

atomic原子类框架

如果不使用JUC中的原子类,那么如果我们要并发地改变Integer等数值,则需要使用锁来控制。而使用java.util.concurrent中的atomic包,则避免了锁的使用,保证线程安全。

  • atomic如何 不加锁保证线程安全
    答案是CAS(compare and swap)或者说它是实现了一种乐观锁。
    实现CAS的类:Unsafe工具类。

Unsafe类与CAS

Unsafe类,顾名思义是一个不安全的类,它提供了一系列不安全的操作方法,这个类只能被一些信任的代码中使用。其主要功能内容包括了:内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等。

  1. unsafe对象的获取
private static final Unsafe theUnsafe;

	@CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

限制了调用此方法的类加载器必须是BootStrap ClassLoader,也就是启动类加载器:负责将存放在<JAVA_HOME>\lib目录中的,或者是被-Xbootclasspath参数所指定的路径中的类库加载到虚拟机中,如rt.jar。这也是其「只能被一些信任的代码中使用」的含义。

  1. 其提供的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);

以compareAndSwapInt为例,参数:

  • Object var1:需要修改的对象
  • long var2:需要修改的字段到对象头的偏移量
  • int var4:期望的值
  • int var5:需要更新到的值

这些CAS方法都是native方法,CAS的本质是靠硬件实现的,是 原子性指令 ,JVM只是封装了汇编调用。

CAS指令的3个操作数:

  • 内存地址V
  • 旧的预期值A(上一次从内存中读取的值)
  • 要修改的新值B

将内存地址为V的值与A比较,如果相等,说明这段时间没有其他的线程修改此值,将内存V值swap为B;否则说明了地址为V的值被更新过了,什么都不做

CAS的问题

使用CAS虽然省去了加锁的上下文切换开销,但也存在着一些问题:

  • 循环时间长时,将一直处于自旋状态,CPU开销大
  • 只能保证对一个共享变量的原子操作
  • ABA问题

总结——原子包实现原理:volatile+循环CAS

可以理解为:

  1. CAS本来是计算机指令层面的一个原子命令,在java中提供了列实现它的原生接口;
  2. 而java中的volatile关键字又保证了不同线程的可见性,为线程之间的通信做了基础;

把这两个特性结合在一起,就是concurrent包实现的基础。往下看会发现Java中一些原子类的实现,大致的模式都是:

  • 声明 共享变量为volatile 修饰,保证线程间可见性;
  • 使用 循环+CAS 原子性来更新值;

1. 基本类型原子类:AtomicInteger

  1. 常见方法的使用:
AtomicInteger ai = new AtomicInteger(0);
System.out.println(ai.addAndGet(2));    //addAndGet:先增加2再返回(2)
System.out.println(ai.getAndAdd(2));    //getAndAdd:先返回原值再增加2(2)
System.out.println(ai.get());           //get:返回值(4)
  1. AtomicInteger类中的变量:
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    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;
    
}
  • value:当前整型值
    注意AtomicInteger中的值是 用volatile关键字修饰 的,保证了一个线程修改其值之后对其他线程可见。
  • valueOffset:字段value的内存偏移地址
    通过字段valueOffset的值可以定位到AtomicInteger对象中value的内存地址。
  1. 方法实现
public final int addAndGet(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}

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

到了unsafe类中:

public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
}
  • getIntVolatile(o, offset)——意义为 从主存中获取 此被volatile修饰的int值
    (参数o:atomicInteger对象;参数offset:内存偏移量)
  • compareAndSwapInt(o, offset, v, v + delta)——即CAS
    (参数v:原始值;参数v + delta:目标值)
    使用while循环实现一个乐观锁,失败时将进入自旋

总结——实现原理:Volatile关键字+CAS

2. 数组类型原子类:AtomicIntegerArray

  1. 常见方法的使用
//初始化原子数组
//方式1:传入已经创建好的int数组参数
int[] array = {1, 2, 3};
AtomicIntegerArray air = new AtomicIntegerArray(array);
//方式2:传入数组长度,默认各个元素值为0,再逐个赋值
AtomicIntegerArray air2 = new AtomicIntegerArray(3);
air2.set(0, 1);
air2.set(1, 2);
air2.set(2, 3);
//给第一个元素+2
air.getAndAdd(0, 2);
  1. AtomicIntegerArray类中的变量
public class AtomicIntegerArray implements java.io.Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final int base = unsafe.arrayBaseOffset(int[].class);
    private static final int shift;
    private final int[] array;

    static {
        int scale = unsafe.arrayIndexScale(int[].class);
        if ((scale & (scale - 1)) != 0)
            throw new Error("data type scale not a power of two");
        shift = 31 - Integer.numberOfLeadingZeros(scale);
    }    
}
  • int[] array:通过int[]数组来保存要操作的数组;
  • base:数组中第一个元素相对于数组起始位置的偏移值;
  • shift:每个int元素内存中的位置;

注意与AtomicInteger不同的是,array数组并没有用volatile来修饰,而是使用了 final关键字 。这样这个数组将被存放在方法区,方法区中的内容是线程共享的,所以同样可以保证多线程的可见性。

  1. 方法实现
public final int getAndAdd(int i, int delta) {
    return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
}
//返回第i个元素的内存偏移量
private long checkedByteOffset(int i) {
    if (i < 0 || i >= array.length)
        throw new IndexOutOfBoundsException("index " + i);

    return byteOffset(i);
}

可知对于AtomicIntegerArray中元素的原子操作,是通过传入一个数组的index值,本质还是到了unsafe中的CAS,原理相同。

Java中的数组,在多线程中只能被当做一个整体,所以在数组的原子操作类中,也是将对数组的操作转化为对单个元素的操作,来保证整个数组的原子性。

  • 为什么不使用volatile修饰数组?
    用volatile修饰的数组,其中的每个元素的读写并不具有volatile的特性。

  • 为什么使用final修饰数组仍然具有volatile的效果?
    用final修饰数组,保证了数组在使用时已经被初始化,且不能指向其他对象;而get、set方法的实现(通过unsafe类的putIntVolatile、getIntVolatile实现)保证了对于数组中每个元素的操作是有volatile特性的。

代码如下:

//方式1的数组赋值:
public AtomicIntegerArray(int[] array) {
        // Visibility guaranteed by final field guarantees
        this.array = array.clone();
    }
//方式2的数组赋值:
public AtomicIntegerArray(int length) {
        array = new int[length];
    }
public final void set(int i, int newValue) {
        unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
    }
//get方法:
public final int get(int i) {
        return getRaw(checkedByteOffset(i));
    }

    private int getRaw(long offset) {
        return unsafe.getIntVolatile(array, offset);
    }
  • 这里有个问题:方式2的数组赋值,使用的是set方法,通过unsafe类的putIntVolatile保证元素的可见性;
    但方式1,直接通过array.clone()来初始化数组,并没有指定每个元素为volatile,如何保证可见性?

首先这里的可见性指:当元素被赋值后立即对其他线程可见。
在方式1中,假如出现了其他线程不可见的情况,必然是有一个线程A正在进行构造函数,另一个线程B正在进行数组的读操作,可以模拟为:

//线程A
public FinalArray() {			//构造函数
        this.array = {1, 2, 3}; //1
    }
//线程B
public void reader() {
        int i = this.array[i]//2
}

与上文的volatile定义的重排序规则类似,final关键字也有一些指令重排序规则:

  • 在构造函数内对一个final引用对象的成员域(代码中的array)的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量(代码中获取array[i]),这两个操作之间不能重排序。

即线程A与B并行执行,A-1代码必然在B-2之前,也就是array元素对其他的线程也是可见的。
假如此时又有一个线程C,也是写操作:

//线程C
public void set() {
       this.array[0] = 8;    //3
}

结果是final可以保证A-1代码必然在B-2之前,却不能保证C-3在B-2之前。如果要确保读线程B看到写线程C对数组元素的写入,那么需要使用同步原语(lock或者volatile)来实现。而AtomicIntegerArray也确实是这么实现的。

总结——实现原理:Final数组+Volatile语义+CAS

3. 引用类型原子类:AtomicReference

假设一种场景,我们不光是要原子性地修改某个数值,而是要原子性地更新多个值。此时就需要原子对象引用。

  1. 常见方法使用
@Data
@AllArgsConstructor
class Reference {
    private String val1;
    private String val2;
}
public static void main(String[] args) {
        Reference reference = new Reference("old1", "old2");
        AtomicReference<Reference> atomicReference = new AtomicReference<Reference>(reference);
        while (true) {
            Reference referenceOld = atomicReference.get();
            Reference referenceNew = new Reference("new1", "new2");
            if (atomicReference.compareAndSet(referenceOld, referenceNew)) {
                reference.setVal1(referenceNew.getVal1());
                reference.setVal2(referenceNew.getVal2());
                break;
            }
        }
        System.out.println(reference.getVal1());
        System.out.println(reference.getVal2());
    }
  1. AtomicReference类中的变量
public class AtomicReference<V> implements java.io.Serializable {
    private static final long serialVersionUID = -1848883965231344442L;

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicReference.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile V value;

    /**
     * Creates a new AtomicReference with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicReference(V initialValue) {
        value = initialValue;
    }
}
  • value:保存了一个 volatile修饰 的value值,即目标对象
  1. 方法实现
/**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(V expect, V update) {
        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
    }

unsafe类中通过CAS比较两个对象是否相等,注意是"=="比较,意味着比较内存地址。

总结——实现原理:Volatile关键字+CAS

4. 字段类型原子类:AtomicIntegerFieldUpdater

  1. 使用场景
    假设一种场景,在一个类中我们只需要原子地修改其中的一个变量,如何实现?
class People {

    private Integer age;
    private String name;

    public People(Integer age) {
        this.age = age;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void increAge() {
        this.age++;
    }
}

public class TestAtomicInterFieldUpdater {

    public static void main(String[] args) {

        People people = new People(0);

        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                people.increAge();
            }).start();
        }
        Thread.sleep(200);
        System.out.println(people.getAge());

    }
}

这段代码运行的结果并不全都是1000,要保证age字段的原子性,首先是使用synchronized关键字:

public synchronized void increAge() {
        this.age++;
    }

如果想使用更轻量级的原子类呢,首先想到的就是AtomicInteger的使用,代码会被修改为这样:

class People {

    //    private Integer age;
    private AtomicInteger age;
    private String name;

    public People(Integer age) {
//        this.age = age;
        this.age = new AtomicInteger(age);
    }

    public Integer getAge() {
//        return age;
        return age.get();
    }

    public void setAge(Integer age) {
//        this.age = age;
        this.age.set(age);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void increAge() {
//        this.age++;
        this.age.incrementAndGet();
    }
}

public class TestAtomicInterFieldUpdater {

    public static void main(String[] args) {

        People people = new People(0);

        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                people.increAge();
            }).start();
        }
        Thread.sleep(200);
        System.out.println(people.getAge());

    }
}

还有另外一种实现方式,就是JUC中给我们提供的原子类:AtomicIntegerFieldUpdater。它可以原子性地修改对象的属性。相对比与AtomicInteger的形式,它对于原有类的修改更小,更符合开发中的开闭原则。也许在对遗留的代码进行修改时比较适合使用这种方式吧。

class People {

	//private Integer age;
    protected volatile int age;
    private String name;

    public People(Integer age) {
        this.age = age;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void increAge() {
        this.age++;
    }
}

class AtomicPeoeple extends People {
    private AtomicIntegerFieldUpdater<People> atomicAge = AtomicIntegerFieldUpdater.newUpdater(People.class, "age");

    public AtomicPeoeple(Integer age) {
        super(age);
    }

    @Override
    public void increAge() {
        this.atomicAge.getAndIncrement(this);
    }
}

public class TestAtomicInterFieldUpdater {

    public static void main(String[] args) throws InterruptedException {

        AtomicPeoeple people = new AtomicPeoeple(0);

        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                people.increAge();
            }).start();
        }
        Thread.sleep(200);
        System.out.println(people.getAge());

    }
}

注意到原子属性age的修饰改为了:protected volatile int age;

  1. AtomicIntegerFieldUpdater对象的创建
AtomicIntegerFieldUpdater.newUpdater(People.class, "age")
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
                                                              String fieldName) {
        return new AtomicIntegerFieldUpdaterImpl<U>
            (tclass, fieldName, Reflection.getCallerClass());
    }

构造参数:

  • tclass:拥有目标属性的类
  • fieldName:要进行原子性操作的目标属性
  • caller:调用类
private static final class AtomicIntegerFieldUpdaterImpl<T>
        extends AtomicIntegerFieldUpdater<T> {
        private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
        private final long offset;
        /**
         * if field is protected, the subclass constructing updater, else
         * the same as tclass
         */
        private final Class<?> cclass;
        /** class holding the field */
        private final Class<T> tclass;

        AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
                                      final String fieldName,
                                      final Class<?> caller) {
            final Field field;
            final int modifiers;
            try {
                field = AccessController.doPrivileged(
                    new PrivilegedExceptionAction<Field>() {
                        public Field run() throws NoSuchFieldException {
                            return tclass.getDeclaredField(fieldName);
                        }
                    });
                modifiers = field.getModifiers();
                sun.reflect.misc.ReflectUtil.ensureMemberAccess(
                    caller, tclass, null, modifiers);
                ClassLoader cl = tclass.getClassLoader();
                ClassLoader ccl = caller.getClassLoader();
                if ((ccl != null) && (ccl != cl) &&
                    ((cl == null) || !isAncestor(cl, ccl))) {
                    sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
                }
            } catch (PrivilegedActionException pae) {
                throw new RuntimeException(pae.getException());
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }

            if (field.getType() != int.class)
                throw new IllegalArgumentException("Must be integer type");

            if (!Modifier.isVolatile(modifiers))
                throw new IllegalArgumentException("Must be volatile type");

            // Access to protected field members is restricted to receivers only
            // of the accessing class, or one of its subclasses, and the
            // accessing class must in turn be a subclass (or package sibling)
            // of the protected member's defining class.
            // If the updater refers to a protected field of a declaring class
            // outside the current package, the receiver argument will be
            // narrowed to the type of the accessing class.
            this.cclass = (Modifier.isProtected(modifiers) &&
                           tclass.isAssignableFrom(caller) &&
                           !isSamePackage(tclass, caller))
                          ? caller : tclass;
            this.tclass = tclass;
            this.offset = U.objectFieldOffset(field);
        }
        ...
  }

它是如何操作一个对象中的某一个属性的?
使用了 反射 ,通过传入的Class和field名来得到成员变量,然后将其在对象中的位置存在offset中。

在构造Atomic对象时进行了一些条件的判读:

  • 判断目标属性是否 对调用类可见 ,即AtomicIntegerFieldUpdater的使用类是否可以操作目标field。这里并没有通过反射设置属性的accessable,因为这就改变了原有类的可见性。
  • 判断目标属性是否是 int基础类型
  • 判断目标属性是否 有volatile 修饰,保证可见性
  • 判断目标属性是否 不是static 的。为什么需要有这个条件限制呢,看构造函数的最后一行:this.offset = U.objectFieldOffset(field);使用了objectFieldOffset来获取成员变量的偏移量,而static修饰的变量有专门的获取偏移量方法:U.staticFieldOffset(field);AtomicIntegerFieldUpdater中并没有对这个地方做判断,所以限制了非static这一条件。
  1. 方法实现

与AtomicInteger等其他的原子类相似,如递增方法的实现:

public int getAndIncrement(T obj) {
        int prev, next;
        do {
            prev = get(obj);
            next = prev + 1;
        } while (!compareAndSet(obj, prev, next));
        return prev;
}

 public final int get(T obj) {
            accessCheck(obj);
            return U.getIntVolatile(obj, offset);
}

总结——实现原理:反射+Volatile关键字+CAS

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值