Java进阶——Java中的Atomic原子特性

引言

这篇文章会从基本概念中入手,首先,从volatile关键字引出原子性的概念和Atomic包,然后,介绍Atomic在使用中的用到的CAS技术和遇到的ABA问题,最后,介绍Atomic的成员和例子

基本概念

一、volatile

用volatile修饰的变量,线程在每次修改变量的时候,都会读取变量修改后的值,可以简单的理解为volatile修饰的变量保存的是变量的地址。volatile变量具有synchronized的可见性,但是不具备原子性

  • 可见性:在多线程并发的条件下,对于变量的修改,其他线程中能获取到修改后的值
  • 原子性:在多线程并发的条件下,对于变量的操作是线程安全的,不会受到其他线程的干扰

volatile不是线程安全的,要使volatile变量提供理想的线程安全,必须同时满足下面两个条件

  • 对变量的写操作不依赖于当前值
  • 该变量没有包含在具有其他变量的不变式中

比如增量操作(x++)看上去类似一个单独操作,实际上它是一个由[读取-修改-写入]操作序列组成的组合操作,必须以原子方式执行,而volatile不能提供必须的原子特性。实现正确的操作,应该使x的值在操作期间保持线程安全,而volatile变量无法实现这点

然而,Java提供了java.util.concurrent.atomic.*包下的变量或引用,让变量或对象的操作具有原子性,在高并发的情况下,依然能保持获取到最新修改的值,常见的有AtomicBoolean、AtomicReference等

  • volatile原理:对于值的操作,会立即更新到主存中,当其他线程获取最新值时会从主存中获取
  • atomic原理:对于值的操作,是基于底层硬件处理器提供的原子指令,保证并发时线程的安全

二、Atomic

Atomic的包名为java.util.concurrent.atomic。这个包里面提供了一组原子变量的操作类,这些类可以保证在多线程环境下,当某个线程在执行atomic的方法时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个线程执行

三、CAS

1、CAS简介

CAS指的是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。这个指令会对内存中的共享数据做原子的读写操作。在Java并发应用中通常指CompareAndSwap或CompareAndSet,即比较并交换,是实现并发算法时常用到的一种技术。java.util.concurrent包中借助CAS实现了区别于synchronized同步锁的一种乐观锁。乐观锁就是每次去取数据的时候都乐观的认为数据不会被修改,因此这个过程不会上锁,但是在更新的时候会判断一下在此期间的数据有没有更新

2、CAS思想

CAS有三个参数,当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false

3、CAS优缺点

  1. 系统在硬件层面保证了CAS操作的原子性,不会锁住当前线程,它的效率是很高的。但是在并发越高的条件下,失败的次数会越多,CAS如果长时间不成功,会极大的增加CPU的开销,因此CAS不适合竞争十分频繁的场景
  2. CAS只能保证一个共享变量的原子操作,对多个共享变量操作时,无法保证操作的原子性,这时就可以用锁,或者把多个共享变量合并成一个共享变量来操作。JDK提供了AtomicReference类来保证引用对象的原子性,可以把多个变量放在一个对象里来进行CAS操作

四、ABA

CAS在操作值的时候检查值是否已经变化,没有变化的情况下才会进行更新。但是如果一个值原来是A,变成B,又变成A,那么CAS进行检查时会认为这个值没有变化,但是实际上却变化了。ABA问题的解决方法是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A就变成1A-2B-3A。JDK提供了AtomicStampedReference来解决ABA问题

Atomic成员

Atomic成员分为四大块

  1. 原子方式更新基本类型
  2. 原子方式更新数组
  3. 原子方式更新引用
  4. 原子方式更新字段

一、原子方式更新基本类型

  • AtomicBoolean:原子更新布尔类型
  • AtomicInteger:原子更新整型
  • AtomicLong:原子更新长整型

简单的看下AtomicInteger提供的方法

方法名 方法作用
get() 直接返回值
set(int) 设置数据
getAndIncrement() 以原子方式将当前值加1,相当于线程安全的i++操作
incrementAndGet() 以原子方式将当前值加1,相当于线程安全的++i操作
getAndDecrement() 以原子方式将当前值减1,相当于线程安全的i–操作
decrementAndGet() 以原子方式将当前值减1,相当于线程安全的–i操作
getAndSet(int) 设置指定的数据,返回设置前的数据
addAndGet(int) 增加指定的数据,返回增加后的数据
getAndAdd(int) 增加指定的数据,返回变化前的数据
lazySet(int) 仅仅当get时才会set
compareAndSet(int, int) 比较源数据和期望数据(参数一),若一致,则设置新数据(参数二)到源数据中并返回true,否则返回false

以AtomicInteger为例

public class Main {

    static AtomicInteger ai = new AtomicInteger(1);

    public static void main(String[] args) {
        //先获取,再自增
        System.out.println(ai.getAndIncrement());
        //先自增,再获取
        System.out.println(ai.incrementAndGet());
        //增加一个指定值,先add,再get
        System.out.println(ai.addAndGet(5));
        //增加一个指定值,先get,再set
        System.out.println(ai.getAndSet(5));
    }
}

输出

1
3
8
8

二、原子方式更新数组

  • AtomicIntegerArray:原子更新整型数组里的元素
  • AtomicLongArray:原子更新长整型数组里的元素
  • AtomicReferenceArray:原子更新引用类型数组里的元素

以AtomicIntegerArray为例

public class Main {

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

    //AtomicIntegerArray内部会拷贝一份数组
    static AtomicIntegerArray ai = new AtomicIntegerArray(valueArr);

    public static void main(String[] args) {
        ai.getAndSet(0, 3);
        //不会修改原始数组value
        System.out.println(ai.get(0));
        System.out.println(valueArr[0]);
    }
}

输出

3
1

三、原子方式更新引用

  • AtomicReference:原子更新引用类型
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段
  • AtomicMarkableReference:原子更新带有标记位的引用类型

以AtomicReference为例

public class Main {

    public static AtomicReference<User> atomicUserRef = new AtomicReference<User>();

    public static void main(String[] args) {
        User user = new User("Jack", 22);
        User updateUser = new User("Rose", 20);

        atomicUserRef.set(user);
        atomicUserRef.compareAndSet(user, updateUser);

        System.out.println(atomicUserRef.get().getName());
        System.out.println(atomicUserRef.get().getOld());
    }

    static class User {
        private String name;
        private int old;

        public User(String name, int old) {
            this.name = name;
            this.old = old;
        }

        public String getName() {
            return name;
        }

        public int getOld() {
            return old;
        }
    }
}

输出

Rose
20

四、原子方式更新字段

  • AtomicIntegerFieldUpdater:原子更新整型字段的更新器
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器
  • AtomicStampedReference:原子更新带有版本号的引用类型

以AtomicIntegerFieldUpdater为例

public class Main {

    private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater
            .newUpdater(User.class, "old");

    public static void main(String[] args) {
        User user = new User("Hensen", 20);
        System.out.println(a.getAndIncrement(user));
        System.out.println(a.get(user));
    }

    public static class User {
        private String name;
        public volatile int old;//注意需要用volatile修饰

        public User(String name, int old) {
            this.name = name;
            this.old = old;
        }

        public String getName() {
            return name;
        }

        public int getOld() {
            return old;
        }
    }
}

输出

22
23

猜你喜欢

转载自blog.csdn.net/qq_30379689/article/details/80785650
今日推荐