专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java并发包-atomic包-AtomicReference全面解析

temp10 2024-10-20 14:42:05 java教程 12 ℃ 0 评论

AtomicReference和前面所讲的AtomicInteger非常的类似,只不过后者是对基本类型int的封装,底层采用的是Unsafe中的compareAndSwapInt来实现的原子操作,而这个方法是比较整型数值是否相等的。而对于这一篇文章的AtomicReference则是调用Unsafe中的compareAndSwapObject()方法来实现原子操作的,这个方法是比较两个对象的地址是否相等的。所以总结如下:

  1. AtomicInteger/AtomicBoolean/AtomicLong是对单一基本变量进行原子操作的,底层是通过比较数值是否相等来实现CAS操作的。
  2. AtomicIntegerArray/AtomicLongArray是对基本类型数组中的元素进行原子操作的,底层也是通过比较数值是否相等来实现CAS操作的,与数组本身没有关系
  3. AtomicIntegerFieldUpdater/AtomicLongFieldUpdater是对对象中的成员基本变量进行原子操作的,底层也是通过比较数值是否相等来实现CAS操作。
  4. AtomicReference是对一个对象地址进行原子操作的,底层是通过比较对象地址是否相同来实现CAS操作的。
  5. AtomicReferenceArray是对对象数组中的元素进行原子操作的,底层是通过比较元素的对象地址是否相同
  6. AtomicReferenceFieldUpdater是对对象中的成员变量进行原子操作的,底层是通过比较成员变量的地址是否相同。

前面3个我已经做了非常详细的说明,所以接下来就全面的分析后面3个是怎样实现的。

Java并发包-atomic包-AtomicReference全面解析

本篇文章的主要内容:

1:AtomicReference实例和源码分析
2:AtomicReferenceArray实例和源码分析
3:AtomicReferenceFieldUpdater原理分析

一、AtomicReference实例和源码分析

我们首先通过一个实例来看一下AtomicReference到底怎么使用。

public class AtomicReferenceTest {
 public static void main(String[] args) {
 AtomicReferenceObj obj1 = new AtomicReferenceObj(10);
 AtomicReferenceObj obj2 = new AtomicReferenceObj(20);
 AtomicReference<AtomicReferenceObj> ar = new AtomicReference<>(obj1);
 obj1.setAge(1);
 ar.compareAndSet(obj1, obj2);
 System.out.println("age=" + ar.get().getAge());
 }
 static class AtomicReferenceObj {
 private Integer age;
 public AtomicReferenceObj(Integer age) {
 this.age = age;
 }
//省略setter和getter方法
 }
}

上面这个实例做如下说明:

1:把对象obj1传递给了AtomicReference
2:将对象obj1中的成员变量age修改成1
3:通过CAS比较并交换数据,如果成功了,则变成obj2,如果失败还是obj1

运行结果如下:

从上面运行结果可以看出最终的结果变成了obj2的age=20,虽然在调用CAS方法之前修改了obj1中的age的值,但是并没有什么影响,这也说明了,AtomicReference底层的CAS是比较对象的地址的,与对象中的成员变量的值是否发生变化没有关系。

如果是对象地址发生了变化,那么结果又是怎样呢?将上面的例子稍微改变如下:

AtomicReferenceObj obj1 = new AtomicReferenceObj(10);
AtomicReferenceObj obj2 = new AtomicReferenceObj(20);
AtomicReferenceObj obj3 = new AtomicReferenceObj(30);
AtomicReference<AtomicReferenceObj> ar = new AtomicReference<>(obj1);
obj1 = obj3;
ar.compareAndSet(obj1, obj2);
System.out.println("age=" + ar.get().getAge());

对上面这个例子说明如下:

1:把obj1传递给了AtomicReference
2:把对象obj3赋值给了对象obj1
3:通过CAS比较并交换数据,如果成功了,则变成obj2,如果失败还是obj1

运行结果如下图:

这个例子的结果打印出了obj1中的age=10,说明第三步CAS操作并没有成功,因为在CAS操作之前,对象ob1变成了obj3,所以引用对象的地址变成了对象obj3,这样CAS就会失败。

通过这两个例子大家知道了AtomicReference在CAS操作时是比较的对象的地址是否发生了变化,与对象中的成员变量无关。

接下来我们从源码角度去分析它的原理所在。

第一步:还是先看看AtomicReference中的成员变量

//封装CAS的Unsafe类
private static final Unsafe unsafe = Unsafe.getUnsafe();
//对象地址在内存的偏移量
private static final long valueOffset;
//原子操作的对象value,被volatile修饰
private volatile V value;
//静态代码块获取对象地址的偏移量
static {
 try {
 valueOffset = unsafe.objectFieldOffset
 (AtomicReference.class.getDeclaredField("value"));
 } catch (Exception ex) { throw new Error(ex); }
}

是不是和AtomicInteger中的成员变量类似,所以它的底层实现也是通过关键字volatile+CAS。

第二步:构造函数

//构造函数1:
public AtomicReference(V initialValue) {
 value = initialValue;
}

初始化value,因为value是volatile,所以对value的写,Java内存模型(JMM)会立刻把value刷新到主存中,会对其他线程可见。

构造函数2:
public AtomicReference() {
}

这个构造函数没有传递任何值给底层的value,所以value=null

第三步:重要的方法

public final boolean compareAndSet(V expect, V update) {
 return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

上面这个方法就是AtomicReference中的CAS方法,它直接调用了Unsafe中的方法,继续跟进。

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

它是一个本地方法,是通过比较对象var1的地址和对象var4的地址是否相同,如果相同,则替换成对象var5,如果不同,则更新失败。所以从源码角度可以看出,AtomicReference是比较的对象的地址是否相同,而AtomicInteger是比较的值是否相同。

通过上面的实例和源码分析后,AtomicReference也是非常的好理解的,接下来我们看看下面与AtomicReference相关的两个类又是怎么回事。

二、AtomicReferenceArray实例和源码分析

前面的文章我们介绍了AtomicIntegerArray,它是对数组中的元素进行原子操作的,与数组本身无关,AtomicReferenceArray是不是也是这个原理呢,答案是肯定的,接下来我们用例子和源码去证明我们的猜测。

public class AtomicReferenceArrayTest {
 public static void main(String[] args) {
 AtomicReferenceArray<User>arr = new AtomicReferenceArray<>(5);
 User user1 = new User(1);
 User user2 = new User(2);
 arr.set(0,user1);
 arr.compareAndSet(0,user1,user2);
 System.out.println("age="+arr.get(0).getAge());
 }
 static class User{
 private Integer age;
 public User(Integer age) {
 this.age = age;
 }
 //省略getter和setter方法
 }
}

运行结果:

从上面的例子中可以看出是对数组的元素进行的操作,所以从实例中也证明了我们猜想,那么从源码角度我们去看一下是否是这样的。

private static final Unsafe unsafe;
//数组中第一个元素的偏移量
private static final int base;
private static final int shift;
//底层的数组
private final Object[] array;

上面的成员变量的这种定义是不是非常的熟悉,在讲解AtomicIntegerArray时就定义了类似的变量。那我们猜想必定有一个静态代码块来获取数组中元素的偏移量,我们继续往下看。

static {
 try {
 unsafe = Unsafe.getUnsafe();
 arrayFieldOffset = unsafe.objectFieldOffset
 (AtomicReferenceArray.class.getDeclaredField("array"));
 base = unsafe.arrayBaseOffset(Object[].class);
 int scale = unsafe.arrayIndexScale(Object[].class);
 if ((scale & (scale - 1)) != 0)
 throw new Error("data type scale not a power of two");
 shift = 31 - Integer.numberOfLeadingZeros(scale);
 } catch (Exception e) {
 throw new Error(e);
 }
}

看到这是不是一目了然,那就明白了AtomicReferenceArray的原理和AtomicIntegerArray的原理是相同的,都是对数组中元素进行原子操作的,与数组本身并没有关系,如果不熟悉这个原理的,请仔细看一下AtomicIntegerArray这一篇文章。

三、AtomicReferenceFieldUpdater原理解析

在分析AtomicIntegerFieldUpdater的时候我们知道它有很多的限制,并且这个类不太常用,那AtomicReferenceFieldUpdater也有这样的限制吗?我们接下来继续分析。

通过查看源码,它的源码结构和AtomicIntegerFieldUpdater类似,也是一个抽象类,通过一个静态方法获取一个实例,如图。

通过上面几篇文章对Java并发包中atomic包主要讲解了两大类:

第一类:通过CAS比较值是否相等,相等则交换。

第二类:通过CAS比较对象地址是否相等,相等则交换。

仔细研究过CAS原理的都知道它有一个问题,就是ABA问题,所以接下来的文章看看JDK是怎样解决这个问题的。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表