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)传入的值会与上一次的比较值对比,然后保留较大者,最后打印出最大值。