Java中Atomic原子类型的详细讲解(二)-刘宇
- 一、AtomicReference介绍
- 1、简述
- 2、简单使用
- 二、CompareAndSwap算法带来的ABA问题
- 1、问题概述
- 2、ABA问题之队列
- 三、AtomicStampedReference介绍
- 1、原理
- 2、源码解释
- 3、练习
- 四、AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray介绍
- 1、创建
- 2、get()
- 3、set()
- 4、getAndSet()
- 5、compareAndSet()
- 五、AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater介绍
- 1、使用前提
- 2、使用场景
- 3、简单的使用
- 4、异常汇总
- 4.1、访问私有字段
- 4.2、访问null对象
- 4.3、访问不存在的属性
- 4.4、访问属性的类型不对应
- 4.5、访问的属性没有使用volatile修饰
作者:刘宇
一、AtomicReference介绍
1、简述
- AtomicReference类提供了一个可以原子读写的对象引用变量。
2、简单使用
package com.brycen.concurrency03.atomic;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
public static void main(String[] args) {
AtomicReference<Simple> atomic = new AtomicReference<Simple>(new Simple("liuyu", 24));
//获取当前值
System.out.println(atomic.get());
//对比并设置值,如果参数1不等于当前值则赋值失败,否则则成功。
boolean result = atomic.compareAndSet(new Simple("liuyu11", 24), new Simple("Brycen", 24));
System.out.println(result);
}
static class Simple {
private String name;
private int age;
public Simple(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Simple [name=" + name + ", age=" + age + "]";
}
}
}
输出结果:
Simple [name=liuyu, age=24]
false
二、CompareAndSwap算法带来的ABA问题
不清楚什么是CAS算法的童鞋们可以看我上一篇博客
1、问题概述
ABA问题就是,当有两个线程对其修改时,线程1在修改之前的值是A,需要修改成B,但是由于此时线程2获得了CPU执行权,将元素值修改成了B,然后又修改成了A。那么当线程1再次获取到CPU执行权的时候进行CAS算法对比时,线程1并未发现此时元素已发生变化,因为他的值是和CAS算法对比之前的值是一样的,这就是ABA问题。ABA问题在int、long等类型并无影响,但是在类似于队列等就会引起严重问题。
2、ABA问题之队列
流程解释:
当线程T2执行完第二步之后将CPU执行权交于T1,而在T1执行第三步弹出A元素之前会进行CAS算法校验。然后发现此时队列中的head元素A存在,即CAS算法通过,即认为队列没有发生变化,那么此时T1线程拿到的队列还是T2操作之前的队列,并没有更新,则进行了第三步弹出A,那么此时队列的队首就会变为B,而B元素在T2线程的时候已经变为了游离状态,那么此时就会出现严重错误。
三、AtomicStampedReference介绍
AtomicStampedReference可以完美解决上述的ABA问题
1、原理
利用stamp印记来表示当前更新的版本,每次更新一次stamp就会+1,在CAS算法对比的时候不光要对比其值是否一致,还需要对比印记编号是否一致,这样就有效解决了ABA问题。
2、源码解释
片段1:
- 就是将要存入的值或引用和stamp更新标签用一个Pair类来封装。
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;//我们要存入的真正的值或引用
this.stamp = stamp;//更新标记
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
片段2:
- expectedReference == current.reference判断当前值或引用和你所传进来的希望值或引用是否一致
- expectedStamp == current.stamp判断当前更新标记和你所传进来的希望更新标记是否一致
- (newReference == current.reference && newStamp == current.stamp) 判断新值或引用是否和旧的一致,并且判断其更新标记是否一致,如果都一致则不会执行casPair来更新,如果上面为false则执行casPair来更新。因为他们是用||来连接的
- casPair(current, Pair.of(newReference, newStamp)更新
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;//获取当前的pair对象
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
3、练习
package com.brycen.concurrency03.atomic;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceTest {
private static AtomicStampedReference<Integer> atomicRef = new AtomicStampedReference<Integer>(100, 0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
//将值100修改为101
boolean success = atomicRef.compareAndSet(100, 101, atomicRef.getStamp(), atomicRef.getStamp()+1);
System.out.println(success);
//再将101修改为100
success = atomicRef.compareAndSet(101, 100, atomicRef.getStamp(), atomicRef.getStamp()+1);
System.out.println(success);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
int stamp = atomicRef.getStamp();
Thread.sleep(2000);
//这边使用了AtomicStampedReference,所以它发现了更新版本号不对,返回false禁止更新,如果使用不带stamped的atomic则会出现更新成功的现象,即ABA问题。
boolean success = atomicRef.compareAndSet(100, 101, stamp, stamp+1);
System.out.println(success);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
输出结果
true
true
false
四、AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray介绍
- 是能够实现对数组支持原子性操作的
- 这里的AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray和前面介绍的非数组的使用差别不大,这边就简单演示一下AtomicIntegerArray的基本用法。基本类型的非数组的使用方法可以查看我的上一篇博客。
- 其实就是比非数组的多了一个传递角标的参数。
1、创建
//第一种创建方式
//指定其数组长度为10
AtomicIntegerArray array = new AtomicIntegerArray(10);
System.out.println(array.length());//输出10
//第二种创建方式
//创建一个数组传递进去
int[] intArray = new int[20];
AtomicIntegerArray array1 = new AtomicIntegerArray(intArray);
System.out.println(array1.length());//输出20
2、get()
AtomicIntegerArray array = new AtomicIntegerArray(10);
//获取第5个元素的值
System.out.println(array.get(5));//输出0
3、set()
AtomicIntegerArray array = new AtomicIntegerArray(10);
//将数组中角标为5的元素设置为100
array.set(5,100);
System.out.println(array.get(5));//输出100
4、getAndSet()
AtomicIntegerArray array = new AtomicIntegerArray(10);
int result = array.getAndSet(5, 200);
System.out.println(result);//输出0
System.out.println(array.get(5));//输出200
5、compareAndSet()
AtomicIntegerArray array = new AtomicIntegerArray(10);
boolean result = array.compareAndSet(5, 0, 100);
System.out.println(result);//输出true
System.out.println(array.get(5));//输出100
其他方法用法和非数组的原子基本类型操作差不多,可参考我上一篇博客,这里就简单介绍了AtomicIntegerArray的一些方法
五、AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater介绍
- 当多线程访问我们类属性的时候,我们可以使用加锁的形式来保证其安全性。那么如果使用无锁的形式的话就可以使用我们的原子类型的FieldUpdater类。
- 原子类型的FieldUpdater类大致分为了AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater,他们之间的用法都大差不差,看过之前的博客的肯定都没问题,这边我们就简单演示一下AtomicIntegerFieldUpdater和AtomicReferenceFieldUpdater。
1、使用前提
- 属性必须使用volatile关键字修饰
- 属性必须是非private、protected(如果是当前类是可以的)
- 属性类型必须和原子类中的类型一致
2、使用场景
- 不想使用锁的时候(包括显示锁或者重量级锁synchronized)
- 大量需要原子类型修饰的对象,如果整体对象使用AtomicStampReference修饰或者属性使用AtomicInteger修饰,都相比较需要消耗大量内存。
3、简单的使用
- 保证了类中属性的原子性
package com.test.atomic;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterTest {
public static void main(String[] args) {
AtomicIntegerFieldUpdater<TestMe> updater = AtomicIntegerFieldUpdater.newUpdater(TestMe.class,"i");
TestMe me = new TestMe();
for (int i=0;i<2;i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<5;i++){
int v = updater.getAndIncrement(me);
System.out.println(Thread.currentThread().getName()+"->"+v);
}
}
}).start();
}
}
static class TestMe{
volatile int i;
}
}
输出结果:
Thread-0->0
Thread-0->1
Thread-0->2
Thread-0->3
Thread-0->4
Thread-1->5
Thread-1->6
Thread-1->7
Thread-1->8
Thread-1->9
4、异常汇总
4.1、访问私有字段
- 如果访问私有变量则会抛出异常
package com.test.atomic;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterFailedTest {
public static void main(String[] args) {
AtomicIntegerFieldUpdater<TestMe> updater = AtomicIntegerFieldUpdater.newUpdater(TestMe.class,"i");
TestMe me = new TestMe();
updater.compareAndSet(me,0,1);
}
static class TestMe{
private volatile int i;
}
}
输出结果:
Exception in thread "main" java.lang.RuntimeException: java.lang.IllegalAccessException: Class com.test.atomic.AtomicIntegerFieldUpdaterFailedTest can not access a member of class com.test.atomic.AtomicIntegerFieldUpdaterFailedTest$TestMe with modifiers "private volatile"
at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.<init>(AtomicIntegerFieldUpdater.java:405)
at java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdater.java:88)
at com.test.atomic.AtomicIntegerFieldUpdaterFailedTest.main(AtomicIntegerFieldUpdaterFailedTest.java:7)
Caused by: java.lang.IllegalAccessException: Class com.test.atomic.AtomicIntegerFieldUpdaterFailedTest can not access a member of class com.test.atomic.AtomicIntegerFieldUpdaterFailedTest$TestMe with modifiers "private volatile"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at sun.reflect.misc.ReflectUtil.ensureMemberAccess(ReflectUtil.java:103)
at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.<init>(AtomicIntegerFieldUpdater.java:394)
... 2 more
4.2、访问null对象
package com.test.atomic;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterFailedTest {
public static void main(String[] args) {
AtomicIntegerFieldUpdater<TestMe> updater = AtomicIntegerFieldUpdater.newUpdater(TestMe.class,"i");
updater.compareAndSet(null,0,1);
}
static class TestMe{
public volatile int i;
}
}
输出结果:
Exception in thread "main" java.lang.ClassCastException
at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.throwAccessCheckException(AtomicIntegerFieldUpdater.java:475)
at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.accessCheck(AtomicIntegerFieldUpdater.java:466)
at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.compareAndSet(AtomicIntegerFieldUpdater.java:488)
at com.test.atomic.AtomicIntegerFieldUpdaterFailedTest.main(AtomicIntegerFieldUpdaterFailedTest.java:8)
4.3、访问不存在的属性
- 在TestMe类中我们的属性是i,而我们更新的属性是i1,则会出现异常。
package com.test.atomic;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterFailedTest {
public static void main(String[] args) {
AtomicIntegerFieldUpdater<TestMe> updater = AtomicIntegerFieldUpdater.newUpdater(TestMe.class,"i1");
updater.compareAndSet(null,0,1);
}
static class TestMe{
public volatile int i;
}
}
输出结果:
Exception in thread "main" java.lang.RuntimeException: java.lang.NoSuchFieldException: i1
at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.<init>(AtomicIntegerFieldUpdater.java:403)
at java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdater.java:88)
at com.test.atomic.AtomicIntegerFieldUpdaterFailedTest.main(AtomicIntegerFieldUpdaterFailedTest.java:7)
Caused by: java.lang.NoSuchFieldException: i1
4.4、访问属性的类型不对应
- 可以看到TestMe中的属性类型是Integer类型,如果我们给的是Long类型则会报错
package com.test.atomic;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class AtomicIntegerFieldUpdaterFailedTest {
public static void main(String[] args) {
AtomicReferenceFieldUpdater<TestMe,Long> updater = AtomicReferenceFieldUpdater.newUpdater(TestMe.class,Long.class,"i");
TestMe me = new TestMe();
boolean result = updater.compareAndSet(me, null, 1l);
System.out.println(result);
}
static class TestMe{
public volatile Integer i;
}
}
输出结果:
Exception in thread "main" java.lang.ClassCastException
at java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl.<init>(AtomicReferenceFieldUpdater.java:343)
at java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(AtomicReferenceFieldUpdater.java:110)
at com.test.atomic.AtomicIntegerFieldUpdaterFailedTest.main(AtomicIntegerFieldUpdaterFailedTest.java:8)
4.5、访问的属性没有使用volatile修饰
- 可以看到我们TestMe中的属性i没有加volatile关键字,那么就会出错。
package com.test.atomic;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class AtomicIntegerFieldUpdaterFailedTest {
public static void main(String[] args) {
AtomicReferenceFieldUpdater<TestMe,Integer> updater = AtomicReferenceFieldUpdater.newUpdater(TestMe.class,Integer.class,"i");
TestMe me = new TestMe();
boolean result = updater.compareAndSet(me, null, 1);
System.out.println(result);
}
static class TestMe{
public Integer i;
}
}
输出结果:
Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type
at java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl.<init>(AtomicReferenceFieldUpdater.java:348)
at java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(AtomicReferenceFieldUpdater.java:110)
at com.test.atomic.AtomicIntegerFieldUpdaterFailedTest.main(AtomicIntegerFieldUpdaterFailedTest.java:8)