java原子类

什么是原子类
原子是最小粒度的,操作不可分割的。

原子类作用
对多线程访问同一个变量,我们需要加锁,而锁是比较消耗性能的,JDk1.5之后, 新增的原子操作类提供了
一种用法简单、性能高效、线程安全地更新一个变量的方式, 这些类同样位于JUC包下的atomic包下,发展
到JDk1.8,该包下共有17个类, 囊括了原子更新基本类型、原子更新数组、原子更新属性、原子更新引用

jdk1.8新增原子类
DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder、Striped64
到目前为止在java.util.concurrent.atomic包下提供了17个原子类
可分为4类:基本原子类,数组原子类、引用原子类、字段原子类

①基本原子类
布尔型:AtomicBoolean
整型:AtomicInteger
长整形:AtomicLong
②数组原子类
整型数组:AtomicIntegerArray
长整型数组:AtomicLongArray
引用型数组:AtomicReferenceArray
③引用原子类
AtomicReference:原子更新引用类型
AtomicReferenceFieldUpdater:原子更新引用类型里的字段
AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方         法是AtomicMarkableReference(V initialRef, boolean initialMark)
④字段原子类
AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
AtomicLongFieldUpdater:原子更新长整型字段的更新器
AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题

基本原子类都是volatile value+cas比较简单略过。
数组原子类以AtomicIntegerArray为例,主要讲述如何根据索引下标定位数组元素?

static {
    // 获取此数组的每个成员的内存偏移量(就是每个对象占内存大小),此处整形返回4
    int scale = unsafe.arrayIndexScale(int[].class);
    if ((scale & (scale - 1)) != 0) // scale必须是2的乘幂
        throw new Error("data type scale not a power of two");
    // numberOfLeadingZeros返回scale位数第一个遇到1的0的个数
    // 0000 0000 0000 0000 0000 0000 0000 0100,此处是29
    shift = 31 - Integer.numberOfLeadingZeros(scale);
}

// 下标i的元素内存偏移地址
private static long byteOffset(int i) {
    // 由于数组使用的是一块连续的内存空间,可以这么计算
    // base = unsafe.arrayBaseOffset(int[].class),获取首个元素的偏移地址
    // 可以证明 i * scale + base == i << shift + base
    return ((long) i << shift) + base;
}

// 延迟设置,设置的值不保证立刻刷入主内存,被其他线程可见,其它线程可能看到值会在稍许的延迟之后
public final void lazySet(int i, int newValue) {
    unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);  
//使用Unsafe.putOrderedObject方法,这个方法在对低延迟代码是很有用的,它能够实现非堵塞的写入,
//这些写入不会被Java的JIT重新排序指令(instruction reordering),这样它使用快速的存储-存储(store- 
//store) barrier, 而不是较慢的存储-加载(store-load) barrier, 后者总是用在volatile的写操作上,
//这种性能提升是有代价的,也就是写后结果并不会被其他线程看到,甚至是自己的线程,通常是几纳秒后
//被其他线程看到,这个时间比较短,所以代价可以忍受。总之牺牲可见性来提升性能。
//关于这一点,可以参考:https://www.cnblogs.com/chenyangyao/p/5269622.html
}

引用原子类,AtomicReference略过,主要介绍AtomicReferenceFieldUpdater和AtomicMarkableReference!
一、AtomicReferenceFieldUpdater特征
①基于反射
②AtomicReferenceFieldUpdater是抽象类,通过静态方法newUpdater(Class<U> tclass, Class<W> vclass, String fieldName)来创建实例对象,实例对象类型是AtomicReferenceFieldUpdater的静态内部类AtomicReferenceFieldUpdaterImpl。其中参数

tclass目标对象的类型
vclass目标字段的类型
fieldName目标字段名

③fieldName必须是volatile,类型修饰符不能是private、static,如果目标对象类型同AtomicReferenceFieldUpdater所在使用类是同包或者目标对象类型在AtomicReferenceFieldUpdater所在使用类中,则fieldName可以是默认修饰符或者protected。不同包必须是public。

public class AtomicTest {
    public static void main(String[] args) {
        AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Dog.class, String.class, "name");
        Dog dog = new Dog();
        updater.compareAndSet(dog, dog.name, "white dog");
        System.out.println(dog.name);
    }
}
class Dog {
    volatile String name = "black dog";
}

二、AtomicMarkableReference与AtomicStampedReference区别
AtomicMarkableReference不关心变量是否被更改过几次,只是单纯想知道是否被更改过,通过boolean值来标识是否被更改,而AtomicStampedReference通过int值来标识变量被更改的次数,可以解决ABA问题。其它功能都相似。
AtomicMarkableReference内部维护了一个private volatile Pair<V> pair,用于存储引用对象和boolean标识。

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 V get(boolean[] markHolder) {
        Pair<V> pair = this.pair;
        markHolder[0] = pair.mark; 
        return pair.reference;
    }

public boolean compareAndSet(V expectedReference, V newReference,
                                 boolean expectedMark,boolean newMark) {
        Pair<V> current = pair;
        // 如果预期值和新值一致返回true,否则更新为新的Pair
        return
            expectedReference == current.reference &&
            expectedMark == current.mark &&
            ((newReference == current.reference &&
              newMark == current.mark) ||
             casPair(current, Pair.of(newReference, newMark)));
    }

public boolean attemptMark(V expectedReference, boolean newMark) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            (newMark == current.mark ||
             casPair(current, Pair.of(expectedReference, newMark)));
    }

字段原子类,以AtomicIntegerFieldUpdater为例,AtomicLongFieldUpdater类似AtomicIntegerFieldUpdater,源码比较简单略过
AtomicIntegerFieldUpdater特征
①字段必须是volatile类型的,在线程之间共享变量时保证立即可见
②调用者拥有操作对象字段的权限,那么就可以反射进行原子操作
③对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段 
④只能是实例变量,不能是类变量,也就是说不能加static关键字 
⑤只能是可修改变量,不能使final变量,因为final的语义就是不可修改 
⑥对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater

// 只验证上述第3点
public class Parent {
    public volatile int age = 50;
}

public class Son extends Parent{
    // public volatile int age = 30;
}

public class AtomicTest {
    public static void main(String[] args) {
        Son son = new Son();
        System.out.println(son.age); // 此处可以访问父类age,打印50
        AtomicIntegerFieldUpdater aifU = AtomicIntegerFieldUpdater.newUpdater(Son.class,"age");
        aifU.set(son, 34);
        System.out.println(son.age);
    }
}

// 运行结果如下:
Exception in thread "main" java.lang.RuntimeException: java.lang.NoSuchFieldException: age

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值