1.分类

java原子类在java.util.concurrent.atomic包下,用于在多线程下保证变量原子操作。

  • 基本类:
    AtomicInteger、AtomicLong、AtomicBoolean、AtomicIntegerArray、AtomicLongArray
  • 引用类型:
    AtomicReference、AtomicReferenceArray、AtomicStampedRerence、AtomicMarkableReference
  • 属性原子修改器(Updater):
    AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
  • 性能
    Striped64、LongAdder、DoubleAdder、LongAccumulator、DoubleAccumulator

2.原理

通过unsafe.compareAndSwap实现,cpu为并发实现了一个指令,java通过unsafe的方式调用,

  • public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
  • public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
  • public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
    对象 内存地址偏移,原值,更新值 更新前比较当前值和原值,相同则更新为新值,返回true,否则不做操作,返回false。
    原子类会循环调用compareAndSwap,直到该方法返回true。

3.使用举例

3.1 AtomicInteger、AtomicLong使用

AtomicInteger、AtomicLong api类似

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());
    }
}

3.2 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());
    }
}

3.3 AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray使用

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

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));;
    }
}

3.4 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.5 AtomicMarkableReference、AtomicStampedReference使用

AtomicMarkableReference跟AtomicStampedReference都是标记一个类,然后根据类的引用以及标记来判断是否允许操作的类。其中AtomicMarkableReference只能标记为true或者false。AtomicStampedReference的标记只能是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));
   }
}

3.6 对象字段操作类AtomicIntegerFieldUpdater,AtomicLongFieldUpdater和AtomicReferenceFieldUpdater

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

  • 对应的字段必须非private修饰
  • 对应的字段必须是volatile修饰的
  • AtomicIntegerFieldUpdater操作的字段必须是int类型,AtomicLongFieldUpdater操作的字段必须是long类型,AtomicReferenceFieldUpdater的操作字段必须是创建AtomicReferenceFieldUpdater对象时候指定字段的类型
    因为这些类对字段进行操作都是利用反射进行操作的,需要用volatile修饰保证可见性。
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;
    }
}

3.7.并发辅助计数类

DoubleAccumulator,LongAccumulator,DoubleAdder,LongAdder

  • DoubleAdder,LongAdder :LongAdder是JDK1.8开始出现的,所提供的API基本上可以替换掉原先的AtomicLong。LongAdder所使用的思想就是热点分离,这一点可以类比一下ConcurrentHashMap的设计思想。就是将value值分离成一个数组,当多线程访问时,通过hash算法映射到其中的一个数字进行计数。而最终的结果,就是这些数组的求和累加
  • DoubleAccumulator,LongAccumulator:LongAccumulator是LongAdder的功能增强版。LongAdder的API只有对数值的加减,而LongAccumulator提供了自定义的函数操作。其构造函数如下:
// accumulatorFunction:需要执行的二元函数(接收2个long作为形参,并返回1个long);identity:初始值
    public LongAccumulator(LongBinaryOperator accumulatorFunction, long identity) {
        this.function = accumulatorFunction;
        base = this.identity = identity;
    }

上面构造函数,accumulatorFunction:需要执行的二元函数(接收2个long作为形参,并返回1个long);identity:初始值。下面看一个Demo:

public class LongAccumulatorDemo {
 
    // 找出最大值
	public static void main(String[] args) throws InterruptedException {
		LongAccumulator accumulator = new LongAccumulator(Long::max, Long.MIN_VALUE);
		Thread[] ts = new Thread[1000];
 
		for (int i = 0; i < 1000; i++) {
			ts[i] = new Thread(() -> {
				Random random = new Random();
				long value = random.nextLong();
				accumulator.accumulate(value); // 比较value和上一次的比较值,然后存储较大者
			});
			ts[i].start();
		}
		for (int i = 0; i < 1000; i++) {
			ts[i].join();
		}
		System.out.println(accumulator.longValue());
	}
}

accumulate(value)传入的值会与上一次的比较值对比,然后保留较大者,最后打印出最大值。