并发举措五之Atomic

        前言

        我们在并发举措之二之volatile中讲过,volatile关键字修饰的变量可以使不用线程之间可见,且volatile 禁止了指令重排。但是volatile关键字修饰的变量在运行中还可能出现线程不安全的情况,根本原因在与对变量的操作不能保证其原子性,java.util.concurrent包提供了一组原子操作的封装类,它们位于java.util.concurrent.atomic包。常见的有AtomicInteger、AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference等,他们的实现原理相同,区别在与运算对象类型的不同。令人兴奋地,还可以通AtomicReference<V>将一个对象的所有操作转化成原子操作。
        我们知道,在多线程程序中,诸如++i 或i++等运算不具有原子性,是不安全的线程操作之一。通常我们会使用synchronized将该操作变成一个原子操作,但JVM为此类操作特意提供了一些同步类,使得使用更方便,且使程序运行效率变得更高。通过相关资料显示,通常AtomicInteger的性能是ReentantLock的好几倍。

      

一、实现机制

       我们以AtomicInteger为例,它提供的主要操作有:

  • 增加值并返回新值:int addAndGet(int delta)
  • 加1后返回新值:int incrementAndGet()
  • 获取当前值:int get()
  • 用CAS方式设置:int compareAndSet(int expect, int update)

     Atomic类是通过无锁(lock-free)的方式实现的线程安全(thread-safe)访问。它的主要原理是利用了CAS:Compare and Set。

     如果我们自己通过CAS编写incrementAndGet(),它大概长这样:

public int incrementAndGet(AtomicInteger var) {
    int prev, next;
    do {
        prev = var.get();
        next = prev + 1;
    } while ( ! var.compareAndSet(prev, next));
    return next;
}

CAS是指,在这个操作中,如果AtomicInteger的当前值是prev,那么就更新为next,返回true。如果AtomicInteger的当前值不是prev,就什么也不干,返回false。通过CAS操作并配合do ... while循环,即使其他线程修改了AtomicInteger的值,最终的结果也是正确的。

二、atomic分类

    基本类型类:用于通过原子的方式更新基本类型
        AtomicBoolean 原子更新布尔类型
        AtomicInteger 原子更新整型
        AtomicLong   原子更新长整型
    数组:通过原子的方式更新数组里的某个元素
        AtomicIntegerArray 原子更新整型数组里的元素
        AtomicLongArray  原子更新长整型数组里的元素
        AtomicReferenceArray 原子更新引用类型数组里的元素
    引用类型:如果要原子的更新多个变量,就需要使用这个原子更新引用类型提供的类
        AtomicReference 原子更新引用类型
        AtomicReferenceFieldUpdater 原子更新引用类型里的字段
        AtomicMarkableReference 原子更新带有标记位的引用类型
    字段类: 如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类
        AtomicIntegerFieldUpdater   原子更新整型的字段的更新器
        AtomicLongFieldUpdater 原子更新长整型字段的更新器
        AtomicStampedReference 原子更新带有版本号的引用类型

 三、atomic应用

  3.1、编写一个多线程安全的全局唯一ID生成器:

class IdGenerator {
    AtomicLong var = new AtomicLong(0);

    public long getNextId() {
        return var.incrementAndGet();
    }
}

3.2、AtomicIntegerArray应用

public class AtomicIntegerArrayTest {

    static int[] value = new int[] { 1, 2 };

    static AtomicIntegerArray ai = new AtomicIntegerArray(value);

    public static void main(String[] args) {
        ai.getAndSet(0, 3);
        System.out.println(ai.get(0));
        System.out.println(value[0]);
    }

}

AtomicIntegerArray类需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响到传入的数组。

3.3、AtomicReference应用

public class AtomicReferenceTest {
    public static void main(String[] args) {
        AtomicReference<Student> atomicReference = new AtomicReference();
        Student student = new Student("张三", 18);
        atomicReference.set(student);
        Student updateStudent = new Student("李四", 19);
        atomicReference.compareAndSet(student, updateStudent);
        System.out.printf("name:%s,age:%d \n",atomicReference.get().getName(),atomicReference.get().getAge());
    }

    static class Student {
        private String name;
        private int age;

        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
    }
}

四、优缺点

  •       CAS相对于其他锁,不会进行内核态操作,有着一些性能的提升。但同时引入自旋,当锁竞争较大的时候,自旋次数会增多。cpu资源会消耗很高。
  •       CAS+自旋适合使用在低并发有同步数据的应用场景。
  •     在Java 8中引入了4个新的计数器类型,LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator。他们都是继承于Striped64。,
  •       在LongAdder 与AtomicLong有什么区别?Atomic*遇到的问题是,只能运用于低并发场景。因此LongAddr在这基础上引入了分段锁的概念。当竞争不激烈的时候,所有线程都是通过CAS对同一个变量(Base)进行修改,当竞争激烈的时候,会将根据当前线程哈希到对于Cell上进行修改(多段锁)。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值