面试题十:AtomicReference和AtomicReferenceFieldUpdater有何异同?

AtomicReference和AtomicReferenceFieldUpdater有何异同?

一、面试官视角:这道题想考察什么?

  • 是否熟练掌握原子操作的概念

    线程安全问题涉及到操作原子性的概念

  • 是否熟悉AR和ARFU这两个类的用法和原理

  • 是否对Java对象的内存占用有认识

  • 是否有较强的敏感度和深入探究的精神

二、题目剖析:

1、AtomicReference的用法

1
2
3
class AtomicReferenceValueHolder {
AtomicReference<String> atomicValue = new AtomicReference<>("HelloAtomic");
}
1
2
3
4
5
6
7
8
9
10
  AtomicReferenceValueHolder holder = new AtomicReferenceValueHolder();
// compareAndSet(cas):本质上是使用的Unsafe类提供的CAS能力
// 如果是“Hello”那么就改成“World”
holder.atomicValue.compareAndSet("Hello", "World");
String value = holder.atomicValue.getAndUpdate(new UnaryOperator<String>(){
@Override
public String apply(String s) {
return "HelloWorld";
}
});

2、AtomicReferenceFieldUpdater的用法

1
2
3
4
class SimpleValueHolder {
public static AtomicReferenceFieldUpdater<SimpleValueHolder, String> valueUpdater = AtomicReferenceFieldUpdater.newUpdater(SimpleValueHolder.class, String.class, "value");
volatile String value = "HelloAtomic";
}
1
2
3
4
5
6
7
8
  SimpleValueHolder holder = new SimpleValueHolder();
SimpleValueHolder.valueUpdater.compareAndSet(holder, "Hello", "World");
String value = SimpleValueHolder.valueUpdater.getAndUpdate(holder, new UnaryOperator<String>(){
@Override
public String apply(String s) {
return "HelloWorld";
}
});

3、AR和ARFU的对比

1
2
3
class AtomicReferenceValueHolder {
AtomicReference<String> atomicValue = new AtomicReference<>("HelloAtomic");
}
1
2
3
4
class SimpleValueHolder {
public static AtomicReferenceFieldUpdater<SimpleValueHolder, String> valueUpdater = AtomicReferenceFieldUpdater.newUpdater(SimpleValueHolder.class, String.class, "value");
volatile String value = "HelloAtomic";
}

发现AR比ARFU写起来的时候代码量要少得多,而且AR不用像ARFU一样需要用到反射,但是很多框架却选择使用ARFU,我们查看源码发现,AR源码里面,本质也有一个private volatile V value; 存在,那么这两者的差异点主要在于AR本身是要指向一个对象的,也就是要比ARFU多创建一个对象,而这个对象的头(Header)占12个字节,它的成员(Fields)占4个字节,也就比ARFU要多出来16个字节,这是对于32位的是这种情况,如果是64位的话,你启用了-XX:+UseComparessedOops 指针压缩的话,那么Header还是占用12个字节,Fields也还是占用4个字节,但如果没有启用指针压缩的话,那么Header是占16个字节,Fields占用8个字节,总共占用24个字节,那么就说明每创建一个AR都会多出来这么多的内存,那么对GC的压力就有很大的影响了。

4、迁移:使用ARFU的例子(1)

1
2
3
4
5
public class BufferedInputSteam extends FilterInputStream {
protected volatile byte[];

private static final AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater = AtomicReferenceFieldUpdater.newUpdater(BufferedInputStream.class, byte[].class, "buf");
}

5、迁移:使用ARFU的例子(2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// lazy有几种线程安全的模式,比如同步的、默认不是线程安全的等等
val value by lazy(LazyThreadSafetyMode.PUBLICATION) {
...
}

// PUBLICATION的实现就用到了ARFU
private class SafePublicationLazyImpl<out T>(initializer:() -> T):Lazy<T>,Serializable {
override val value:T
get() {
...
val newValue = initializerValue()
if(valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) {
...
}
...
}
companion object {
private val valueUpdater = AtomicReferenceFieldUpdater.newUpdater(...)
}
}

三、题目结论:

  • AR和ARFU的功能一致,原理相同,都是基于Unsafe的CAS操作

  • AR通常作为对象的成员使用,占16B(指针压缩)或24B(指针不压缩),取决于多少位的虚拟机以及有没有开启指针压缩

  • ARFU通常作为类的静态成员使用,对实例成员进行修改,这样做的作用就是节省内存

  • AR使用更友好,ARFU更适合类实例比较多的场景(比如写网络框架的时候肯定会用到ARFU)

  • Kotlin协程的实现,因为也是需要对这个结果保证操作原子性的,所以它的实现也用到了ARFU