6.juc包下的原子类AtomicInteger,AtomicLong等AtomicXXX介绍

 在介绍juc中的原子类之前,先看看官方文档对java.util.concurrent.atomic包的介绍官方文档地址这里截取翻译之后的部分描述

1. 支持对单个变量进行无锁线程安全编程
2. 类的实例`AtomicBoolean`,`AtomicInteger`,`AtomicLong`和`AtomicReference` 每个提供访问和更新相应的类型的单个变量
3. 这些类不是 java.lang.Integer和相关类的通用替代品。他们没有 定义方法,如equals,hashCode和 compareTo。由于预期原子变量会发生突变,因此对于哈希表键,它们是较差的选择。
4. `AtomicIntegerArray`,`AtomicLongArray`和`AtomicReferenceArray`类进一步扩展到这些类型的数组原子操作的支持
5. `AtomicIntegerFieldUpdater`,`AtomicLongFieldUpdater`和`AtomicReferenceFieldUpdater`是基于反射的实用程序,它们提供对关联的字段类型的访问。这些主要用于原子数据结构,其中几个volatile同一节点的字段(例如,树节点的链接)独立地接受原子更新。这些类在如何以及何时使用原子更新方面提供了更大的灵活性,但代价是基于反射的设置更加笨拙,使用不方便且保证较弱。
6. `AtomicMarkableReference`类与引用关联的单个布尔值。例如,此位可能在数据结构内使用,表示所引用的对象在逻辑上已被删除。`AtomicStampedReference`类与引用关联的整数值。例如,这可以用于表示与一系列更新相对应的版本号。

 在整个包中可以分为5种,这5中是我个人按照作用进行分类的

种类包含的类
普通原子操作类AtomicBoolean,AtomicInteger,AtomicLongAtomicReference
数组操作类AtomicIntegerArray,AtomicLongArrayAtomicReferenceArray
对象中字段操作类AtomicIntegerFieldUpdater,AtomicLongFieldUpdaterAtomicReferenceFieldUpdater
标记操作类AtomicMarkableReferenceAtomicStampedReference
并发辅助计算类DoubleAccumulator,DoubleAdder,LongAccumulator,LongAdder以及这些类的抽象父类Striped64
1.作用

 从上面介绍中可以看出这个类的作用是用来对单个变量进行操作的,在并发环境下可以保证线程的安全性,其中保证安全的原因是使用CAS操作对变量进行操作的,对于这种CAS操作的实现可以查看前面的一篇文章CAS以及相关的底层实现

2.普通原子操作类

 个人划分的普通原子操作类是AtomicBoolean,AtomicInteger,AtomicLongAtomicReference各个类的作用分别是

类名作用
AtomicBoolean一个可以原子更新值boolean值的类
AtomicInteger一个可以原子更新int类型变量值的类
AtomicLong一个可以原子更新long类型变量值的类
AtomicReference一个可以原子更新对象引用的类
2.1 AtomicInteger,AtomicLong

AtomicLong中的方法跟 AtomicInteger中提供的方法是一模一样的,这里用代码简单演示一下AtomicLong中的方法

public class AtomicTest {
    public static void main(String[] args) {
        AtomicInteger integer = new AtomicInteger();
        //设置值
        integer.set(1);
        //跟set一样也是设置值,只不过set方法能保证可见性,而lazySet不行
        integer.lazySet(2);
        //CAS操作,比较交换
        integer.compareAndSet(2,3);
        //自增后获取值
        integer.incrementAndGet();
        //与指定的值相加后返回
        integer.addAndGet(2);
        //获取之后进行自增
        integer.getAndIncrement();
        System.out.println(integer.get());
    }
}

 这里只列举了部分的方法,大部分方法的作用看方法名就能明白。关于set方法跟lazySet的区别,这里可以看前面写的一篇文章java的JUC包下AtomicXXX中的set跟lazySet区别以及lazySet的原理

2.2 AtomicBoolean

AtomicBoolean其实是用一个int类型的值来表示truefalse的,如果是true则用1表示,如果是false则用0表示。其初始化方法就可以看出来

    public AtomicBoolean(boolean initialValue) {
        value = initialValue ? 1 : 0;
    }

 因此其底层的实现的方式跟AtomicBoolean是一样的,还是简单的列举一下部分方法

public class AtomicBooleanTest {
    public static void main(String[] args) {
        AtomicBoolean atomicBoolean = new AtomicBoolean(true);
        System.out.println(atomicBoolean.get());
        boolean setResult1 = atomicBoolean.compareAndSet(false, true);
        System.out.println(setResult1);
        boolean setResult2 = atomicBoolean.compareAndSet(true, false);
        System.out.println(setResult2);
        System.out.println(atomicBoolean.get());
    }
}

运行结果

true
false
true
false
2.3 AtomicReference

AtomicReference作用是更新一个对象。维护的是这个变量的地址值。在更新的时候校验这个的对象是不是初始化时候的对象的引用地址,不是则不给予更新,是的就更新。同时还可以定义更新的操作函数。这里列举部分测试代码

public class AtomicReferenceTest {
    @Test
    public void test() {
        Object referenceOne = new Object();
        Object referenceTwo = new Object();
        AtomicReference<Object> atomicReferenceOne = new AtomicReference<>(referenceOne);
        System.out.println(atomicReferenceOne.get());
        //compareAndSet时候原始的对象必须是创建AtomicReference的时候设置的对象
        boolean resultOne = atomicReferenceOne.compareAndSet(referenceTwo, referenceOne);
        System.out.println(resultOne);
        //compareAndSet时候原始的对象必须是创建AtomicReference的时候设置的对象
        boolean resultTwo = atomicReferenceOne.compareAndSet(referenceOne, referenceTwo);
        System.out.println(resultTwo);
        //获取然后更新函数执行结果返回的值
        atomicReferenceOne.getAndUpdate((one -> referenceTwo));
        System.out.println(atomicReferenceOne.get());
    }
}
3.数组操作类

 用来操作数组的原子类AtomicIntegerArray,AtomicLongArrayAtomicReferenceArray。分别对应操作int数组,long数组以及object类型数组。

3.1AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

 这里用AtomicIntegerArray作为示例,因为这两个类实现是一样的,只不过接收的数据类型不一有。先看看对应的重要部分,偏移量的计算方式

 static {
 		//获取当前数组的规模
        int scale = unsafe.arrayIndexScale(long[].class);
        //数组的大小需要是2的倍数
        if ((scale & (scale - 1)) != 0)
            throw new Error("data type scale not a power of two");
        //计算当前数组的长度是2的多少倍
        shift = 31 - Integer.numberOfLeadingZeros(scale);
    }

    private static long byteOffset(int i) {
    	//计算当前元素的实际偏移量=数组的起始偏移量+当前元素在数组的位置
        return ((long) i << shift) + base;
    }

 知道了计算偏移量的方式,就好操作数组中的对应的元素了。所有的对数组中数据的操作,都是通过偏移量来进行的。

public class AtomicIntegerArrayTest {
    @Test
    public void test() {
        AtomicIntegerArray array = new AtomicIntegerArray(new int[]{1, 2, 3, 4,5});
        //获取指定index=2的数据
        System.out.println(array.get(2));
        //在指定index=2的数据上加上1后获取结果
        System.out.println(array.addAndGet(2,1));
        //获取指定index=2的数据
        System.out.println(array.get(2));
        //比较交换指定index=2的值为4,因为值是4而期望的3所以失败
        System.out.println(array.compareAndSet(2,3,4));;
        //比较交换指定index=2的值为3,因为值是4期望的4所以成功
        System.out.println(array.compareAndSet(2,4,3));;
    }
}
4.对象字段操作类
4.1AtomicIntegerFieldUpdater,AtomicLongFieldUpdaterAtomicReferenceFieldUpdater

AtomicIntegerFieldUpdater,AtomicLongFieldUpdaterAtomicReferenceFieldUpdater都是对一个指定对象中的指定字段进行操作。这里需要注意以下几点

  1. 对应的字段必须非private修饰
  2. 对应的字段必须是volatile修饰的
  3. AtomicIntegerFieldUpdater操作的字段必须是int类型,AtomicLongFieldUpdater操作的字段必须是long类型,AtomicReferenceFieldUpdater的操作字段必须是创建AtomicReferenceFieldUpdater对象时候指定字段的类型

 这里解释一下为什么要满足上面几点。

  1. 因为这些类对字段进行操作都是利用反射进行操作的。
  2. 要保证原子性以及可见性就需要用volatile修饰
  3. AtomicIntegerFieldUpdater会判断操作字段是不是int类型,同理AtomicLongFieldUpdater会判断是不是long类型

 列举一下部分方法以及使用方式

public class AtomicXXXFieldUpdaterTest {
    @Test
    public void test() {
        TestObject objectOne = new TestObject();
        //创建一个指定对象指定字段的操作的AtomicIntegerFieldUpdater
        AtomicIntegerFieldUpdater<TestObject> integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(TestObject.class, "age");
        //compareAndSet指定对象的某个int类型字段
        System.out.println(integerFieldUpdater.compareAndSet(objectOne,23,22));
        System.out.println(integerFieldUpdater.compareAndSet(objectOne,22,23));
        //创建一个指定对象指定字段的操作的AtomicLongFieldUpdater
        AtomicLongFieldUpdater<TestObject> longFieldUpdater = AtomicLongFieldUpdater.newUpdater(TestObject.class, "years");
        //compareAndSet指定对象的某个long类型字段
        System.out.println(longFieldUpdater.compareAndSet(objectOne,2019L,2018L));
        System.out.println(longFieldUpdater.compareAndSet(objectOne,2018L,2019L));
        //创建一个指定对象指定类型字段的操作的AtomicReferenceFieldUpdater
        AtomicReferenceFieldUpdater<TestObject,String> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(TestObject.class,String.class, "name");
        //compareAndSet指定对象的指定类型字段
        System.out.println(referenceFieldUpdater.compareAndSet(objectOne,"szh","acy"));
        System.out.println(referenceFieldUpdater.compareAndSet(objectOne,"acy","szh"));
    }
}

class TestObject{
    protected volatile String name="acy";
    public volatile int age=22;
    protected volatile long years=2018L;

    public Long getYears() {
        return years;
    }

    public void setYears(Long years) {
        this.years = years;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
5. 标记操作类
5.1 AtomicMarkableReferenceAtomicStampedReference

AtomicMarkableReferenceAtomicStampedReference都是标记一个类,然后根据类的引用以及标记来判断是否允许操作的类。其中AtomicMarkableReference只能标记为true或者falseAtomicStampedReference的标记只能是int类型的,通常作为更新的版本号,也就是用来解决ABA问题。两者的实现方式相同,都是维护一个内部类分别记录对象以及版本号。简单介绍一下用法。

public class AtomicXXXFieldUpdaterTest {
    @Test
    public void test() {
        Object objOne = new Object();
        Object objTwo = new Object();
        AtomicMarkableReference<Object> markableReference = new AtomicMarkableReference<>(objOne, true);
        //设置标记需要reference符合
        System.out.println(markableReference.attemptMark(objTwo,false));
        System.out.println("设置mark需要reference符合"+markableReference.attemptMark(objOne,false));
        //设置成功需要reference跟标记都是符合的
        System.out.println(markableReference.compareAndSet(objTwo,objOne,false,true));
        System.out.println(markableReference.compareAndSet(objTwo,objOne,true,false));
        System.out.println("设置成功需要reference跟mark都是符合的-----"+markableReference.compareAndSet(objOne,objTwo,false,true));
        AtomicStampedReference<Object> stampedReference = new AtomicStampedReference<>(objOne, 0);
        //设置标记需要reference符合
        System.out.println(stampedReference.attemptStamp(objTwo,2));
        System.out.println("设置stamp需要reference符合"+stampedReference.attemptStamp(objOne,1));
        //设置成功需要reference跟标记都是符合的
        System.out.println(stampedReference.compareAndSet(objTwo,objOne,0,1));
        System.out.println(stampedReference.compareAndSet(objTwo,objOne,1,0));
        System.out.println("设置成功需要reference跟stamp都是符合的------"+stampedReference.compareAndSet(objOne,objTwo,1,0));
    }
}
6.并发辅助计算类
6.1Striped64,DoubleAccumulator,DoubleAdder,LongAccumulator,LongAdder

Striped64是一个可以在并发环境下面计数用的一个组件。这个类的思想可以参考ConcurrentHashMapsize方法。Striped64的设计思路是在竞争激烈的时候尽量分散竞争,在实现上,Striped64维护了一个base Count和一个Cell数组,计数线程会首先试图更新base变量,如果成功则退出计数,否则会认为当前竞争是很激烈的,那么就会通过Cell数组来分散计数,Striped64根据线程来计算哈希,然后将不同的线程分散到不同的Cell数组的index上,然后这个线程的计数内容就会保存在该Cell的位置上面,基于这种设计,最后的总计数需要结合base以及散落在Cell数组中的计数内容。这里可以参考一下这篇博文并发之Striped64
DoubleAccumulator,DoubleAdder,LongAccumulator,LongAdder都是Striped64的字类。主要是对数相加跟求和以及转化上的方法。这里就不多介绍了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值