JUC并发包之Atomic原子包使用说明(CAS算法、ABA问题)

PS:本文基于JDK8

Java从JDK5之后,可以使用java.util.concurrent.atomic包在无锁的情况下进行原子操作,通过Java的Unsafe工具类型调用CPU的原子指令完成,因CPU架构和指令的不同,也有可能出现堵塞的问题,通過自旋 + CAS(乐观锁)保证原子性。

其原理是使用CAS(Compare and Swap)算法,即比较再交换,在这个过程中,通过compareAndSwapInt比较更新value值,如果更新失败,重新获取旧值,然后更新。是一种无锁的非阻塞算法的实现,是硬件对于并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。JUC包中的类使用CAS算法实现了一种乐观锁,JDK5之前是使用synchronized关键字保证同步,这是一种独占锁,也是是悲观锁。(可参考:悲观锁和乐观锁)

原子操作只能使用在单线程中,在多线程中可能出现“ABA问题”,可以使用标记原子引用类型AtomicStampedReference或AtomicMarkableReference处理解决。

atomic包中共有18个操作类,可分为六大类型:

  1. 基本类型
    1. AtomicBoolean:原子布尔类型
    2. AtomicInteger:原子整型
    3. AtomincLong:长整型
  2. 数组类型
    1. AtomicIntegerArray:原子整数数组
    2. AtomicLongArray:原子长整数数组
    3. AtomicReferenceArray:原子引用数组
  3. 引用类型
    1. AtomicReference:基本应用类型
    2. AtomicMarkableReference:标记应用类型
    3. AtomicStampedReference:邮戳引用类型
  4. 对象属性更新器
    1. AtomicIntegerFieldUpdater:对象内整型
    2. AtomicLongFieldUpdater:对象内长整型
    3. AtomicReferenceFieldUpdater :对象内引用类型
  5. 累加器(JDK8)
    1. DoubleAccumulator:原子双精度浮点类型函数累加器
    2. DoubleAdder:原子双精度浮点类型累加器
    3. LongAccumulator:原子长整型函数累加器
    4. LongAdder:原子长整型累加器
  6. 标记原子引用类型(处理ABA问题)
    1. AtomicStampedReference:整型标记原子引用类型
    2. AtomicMarkableReference:布尔型标原子记引用类型

基本用法

1.基本类型操作


import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntUnaryOperator;

public class BaseAtomic {
    /**
     * 初始化为0
     */
    private static AtomicInteger atomicInteger = new AtomicInteger(0);

    public static void main(String[] args) {
        //设置当前值为1
        atomicInteger.set(1);
        //返回当前值;
        atomicInteger.get();
        //返回当前值并做原子加1;
        atomicInteger.getAndIncrement();
        //做原子加1并返回变更后的值;
        atomicInteger.incrementAndGet();
        //返回当前值并做原子加n;
        atomicInteger.getAndAdd(10);
        //做原子加n并返回变更后的值;
        atomicInteger.addAndGet(10);
        //返回当前值并做原子减1;
        atomicInteger.getAndDecrement();
        //做原子减1并返回变更后的值;
        atomicInteger.decrementAndGet();
        //设置为2,本地内存操作,不保证线程可见
        atomicInteger.lazySet(2);

        //原子性函数修改后并返回变更后的值
        IntUnaryOperator intUnaryOperator = operand -> operand + 2;
        atomicInteger.updateAndGet(intUnaryOperator);
    }
}

2.数组类型操作

import java.util.concurrent.atomic.AtomicLongArray;
import java.util.function.LongBinaryOperator;

public class ArrayAtomic {
    /**
     * 初始化为数组长度
     */
    private static AtomicLongArray atomicLongArray = new AtomicLongArray(2);

    public static void main(String[] args) {
        //输出数组长度
        atomicLongArray.length();
        //设置数组索引0上的值为1
        atomicLongArray.set(0, 1L);
        //获取数组索引上的值
        atomicLongArray.get(0);
        //数组索引0上的值加1,并返回变更后的结果
        atomicLongArray.incrementAndGet(0);
        //返回数组索引0上的值,并在数组中的值加1
        atomicLongArray.getAndIncrement(0);
        //数组索引0上的值减1,并返回变更后的结果
        atomicLongArray.decrementAndGet(0);
        //返回数组索引0上的值,并在数组中的值减1
        atomicLongArray.getAndDecrement(0);
        //数组索引0上的值加2,并返回变更后的结果
        atomicLongArray.addAndGet(0, 2L);
        //返回数组索引0上的值,并在数组中的值加2
        atomicLongArray.getAndAdd(0, 2L);
        //数组中索引0上的数与1进行比较,
        //相同着返回true,并修改数组中的值为3;
        //不相同返回false,不做修改.
        atomicLongArray.compareAndSet(0, 1L, 3L);
        //与compareAndSet一致
        atomicLongArray.weakCompareAndSet(0, 1L, 3L);

        //变更函数
        LongBinaryOperator longBinaryOperator = (left, right) -> left+right;
        //返回数组中索引0上的值,并根据函数结果变更数组中的值
        atomicLongArray.getAndAccumulate(0, 2L, longBinaryOperator);
        //根据函数结果变更数组中的值,并返回数组中索引0上变更后的值
        atomicLongArray.accumulateAndGet(0, 2L, longBinaryOperator);
    }
}

3.引用类型操作

import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BinaryOperator;
import java.util.function.UnaryOperator;

public class ReferenceAtomic {
    /**
     * 初始化为0
     */
    private static AtomicReference reference = new AtomicReference<>();

    public static void main(String[] args) {
        User user1 = new User("a", 1);
        //设置引用对象为user
        reference.set(user1);
        //本地内存设置,不保证线程可见
        reference.lazySet(user1);
        //获取user对象
        reference.get();
        //获取并变更为user2
        User user2 = new User("b", 2);
        reference.getAndSet(user2);

        //一元操作函数
        UnaryOperator unaryOperator = new UnaryOperator(){
            @Override
            public Object apply(Object o) {
                User user1 = (User) o;
                user1.setAge(11);
                return user1;
            }
        };
        //返回当前引用,并对将引用对象修改为一元操作函数结果
        reference.getAndUpdate(unaryOperator);

        //二元擦破在函数
        BinaryOperator binaryOperator = new BinaryOperator(){
            /**
             *
             * @param o 变更前引用对象
             * @param o2 传入的对象
             * @return
             */
            @Override
            public Object apply(Object o, Object o2) {
                User user1 = (User) o;
                User user2 = (User) o2;
                user1.setAge(user2.age);
                return user1;
            }
        };
        //获取当前引用对象,并将引用对象更新为二元函数返回的对象.
        reference.getAndAccumulate(user1,binaryOperator);
        //将引用对象更新为二元函数返回的对象,并获取变更后的引用对象
        reference.accumulateAndGet(user2,binaryOperator);
        //当前引用对象与user2比较
        //相等返回true,并设置引用对象为user2
        //不相等返回false,不做变更
        reference.compareAndSet(user1,user2);
        //与compareAndSet一致
        reference.weakCompareAndSet(user1,user1);
    }

    private static class User {
        private String userName;
        volatile int age;

        User(String userName, int age) {
            this.userName = userName;
            this.age = age;
        }

        void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "userName='" + userName + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

4.对象属性更新器

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.function.IntBinaryOperator;
import java.util.function.IntUnaryOperator;

public class ReferenceFieldAtomic {
    /**
     * 初始化为0
     */
    private static final AtomicIntegerFieldUpdater REFERENCE = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");

    public static void main(String[] args) {
        User user = new User("a", 1);
        //设置引用对象为user属性age为2
        REFERENCE.set(user,2);
        //本地内存设置,不保证线程可见
        REFERENCE.lazySet(user,2);
        //返回user对象中的age
        REFERENCE.get(user);
        //返回user对象的age属性,并变更为age为2
        REFERENCE.getAndSet(user,2);
        //返回对象user属性age的值,并修改age加1
        REFERENCE.getAndIncrement(user);
        //修改对象user属性age的值加1,并返回age
        REFERENCE.incrementAndGet(user);
        //返回对象user属性age的值,并修改age减1
        REFERENCE.getAndDecrement(user);
        //修改对象user属性age的值减1,并返回age
        REFERENCE.decrementAndGet(user);
        //一元操作函数
        IntUnaryOperator intUnaryOperator = operand -> ++operand;
        //返回当前user对象的age值,并将user的age值修改为一元操作函数结果
        REFERENCE.getAndUpdate(user,intUnaryOperator);
        //将user的age值修改为一元操作函数结,并返回更改后user对象的age值
        REFERENCE.updateAndGet(user,intUnaryOperator);

        //二元擦破在函数
        //left:传入对象属性age的值
        //right:传入的第二个参数值
        IntBinaryOperator binaryOperator = (left, right) -> left+left;
        //获取修改前user的age的值,并将引用age更新为二元函数返回的对象.
        REFERENCE.getAndAccumulate(user,2,binaryOperator);
        //将引用user中属性值age更新为二元函数返回的值,并获取变更后的age值
        REFERENCE.accumulateAndGet(user,2,binaryOperator);
        //当前引用对象user属性age与1比较
        //相等返回true,并设置引用对象user属性age为user2
        //不相等返回false,不做变更
        REFERENCE.compareAndSet(user,1,2);
        //与compareAndSet一致
        REFERENCE.weakCompareAndSet(user,1,2);
    }

    private static class User {
        private String userName;
        volatile int age;

        User(String userName, int age) {
            this.userName = userName;
            this.age = age;
        }
        @Override
        public String toString() {
            return "User{" +
                    "userName='" + userName + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

5.累加器

import java.util.concurrent.atomic.LongAdder;

public class AdderAtomic {
    /**
     * 生成累加器,起始默认为0
     */
    private static LongAdder longAdder = new LongAdder();

    public static void main(String[] args) {
        //返回统计结果
        longAdder.sum();
        //返回累计结果并重置累加器
        longAdder.sumThenReset();
        //累加1
        longAdder.increment();
        //累减1
        longAdder.decrement();
        //指定累加值
        longAdder.add(2);
        //重置累加器
        longAdder.reset();
    }
}

6.标记原子引用类型(处理ABA问题)

import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AbaAtomic {
    /**
     * 初始化整型标记引用类型
     * 操作类型数据类型为Integer
     * 初始化值为0
     * 初始化版本为1
     */
    private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(0,1);

    /**
     * 初始化标记布尔型引用类型
     * 操作类型数据类型为Integer
     * 初始化值为0
     * 初始化标记为false
     */
    private static AtomicMarkableReference atomicMarkableReference = new AtomicMarkableReference(0,false);

    public static void main(String[] args) {

        //返回当前标记数值
        System.out.println("getStamp:"+atomicStampedReference.getStamp());
        //返回当前引用对象值
        System.out.println("getReference:"+atomicStampedReference.getReference());

        //返回当前引用对象值,并将标记数值存入传入的整型数据0的未位置.
        //传入参数-整型数组,会返回当前版本值
        int[] stampNum = new int[1];
        System.out.println("get:"+atomicStampedReference.get(stampNum));
        System.out.println("stampNum:"+stampNum[0]);

        //设置新标记,
        //参数顺序-当前引用对象,新标记
        //传入对象相等,更新标记,返回true
        //不相等返回false
        System.out.println("attemptStamp :"+atomicStampedReference.attemptStamp(0,2));
        System.out.println("attemptStamp num:"+atomicStampedReference.getStamp());

        //新值新标记
        //参数顺序-引用对象的值,标记版本值
        atomicStampedReference.set(1,2);
        System.out.println("set num:"+atomicStampedReference.getStamp());

        //比较后替换,参数顺序-当前值,替换的值,当前标记值,新的标记值.
        //比较相等替换,返回true
        //比较不相等,返回false
        System.out.println("compareAndSet:"+atomicStampedReference.compareAndSet(1,2,2,3));
        System.out.println("compareAndSet num:"+atomicStampedReference.getStamp());

        //同compareAndSet
        atomicStampedReference.weakCompareAndSet(2,3,3,4);
        System.out.println("weakCompareAndSet num:"+atomicStampedReference.getStamp());

        //atomicMarkableReference操作与atomicStampedReference基本相同
        //返回布尔型标记值
        System.out.println("isMarked:"+atomicMarkableReference.isMarked());
//        输出:
//        getStamp:1
//        getReference:0
//        get:0
//        stampNum:1
//        attemptStamp :true
//        attemptStamp num:2
//        set num:2
//        compareAndSet:true
//        compareAndSet num:3
//        weakCompareAndSet num:4
//        isMarked:false
    }
}

使用效果:

AtomicInteger与volatile修饰符使用情况测试如下,多线程中AtomicInteger保证原子性,volatile无法保证原子性

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicAndVolatie {
    /**
     * 初始化原子整数类型对象
     */
    private static AtomicInteger atomicInteger = new AtomicInteger(0);
    /**
     * 普通线程整数变量
     */
    private static volatile int num;

    static {
        num = 0;
    }

    public static void main(String[] args) {
        //并发个数
        int count = 0;
        //线程并发次数
        int max = 1000;
        while (count < max) {
            new Thread(() -> {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicInteger.getAndIncrement();
            }).start();
            count++;
        }
        count = 0;
        while (count < max) {
            new Thread(() -> {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num++;
            }).start();
            count++;
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("AtomicInteger num=" + atomicInteger.get());
        System.out.println("volatile num=" + num);

//        输出:
//        AtomicInteger num=1000
//        volatile num=993
    }
}

ABA问题

当两个以上线程对同一个对象进行原子操作时可能会出现,如下图:

JUC并发包之Atomic原子包使用说明(CAS算法、ABA问题)_第1张图片

步骤说明:

步骤(1)(2):线程t1读取主内存值为A

步骤(3)(4):线程t2读取主内存值为A

步骤(5)(6):线程t2进行CAS操作,将A改为B

步骤(7)(8):线程t2进行CAS操作,将B改为A

步骤(9)(10):线程t1进行CAS操作,将A改为C

虽然t1原子操作修改成功,但t1的A与t2的ABA只有最终值一致,相关信息已经发生变化,如队列中的前后顺序等。

通过使用AtomicStampedReference类中的Stamp标记可以区分数据版本是否一致,避免ABA问题的出现。

你可能感兴趣的:(java)