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(); 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
| val value by lazy(LazyThreadSafetyMode.PUBLICATION) { ... }
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