随笔分类
AtomicXXXFieldUpdater技巧
考虑一种情况,假如我们的类中需要假如定义两个或者更多的原子类型,仅仅将类本身去定义为原子类型去操作它是远远不够的,通常把我们可能会去使用 AtomicReference的方式使其变成原子类,然后我们再去进行相关的原子操作
实际上,这是一种比较糟糕的做法,最直观的便是内存上的问题,因此这里介绍下 AtomicXXXReferenceUpdater的玩法
以 Reactor#LambdaSubscriber为例:
// 底层
volatile Subscription subscription;
// AtomicXXXFieldUpdater的新玩法 - 于此方式去赋予 subscription的方式
static final AtomicReferenceFieldUpdater<LambdaSubscriber, Subscription> S =
AtomicReferenceFieldUpdater.newUpdater(LambdaSubscriber.class,
Subscription.class,
"subscription");
首先定义一个 volatile修饰的引用变量,然后这个变量被加入到原子管理的字段中去,其实也就是 LambdaSubscriber类下的类型为 Subscription的名称为 subscription修饰的变量,以来得到变量 S
接着看:
@CallerSensitive
public static <U,W> AtomicReferenceFieldUpdater<U,W> newUpdater(Class<U> tclass,
Class<W> vclass,
String fieldName) {
return new AtomicReferenceFieldUpdaterImpl<U,W>
(tclass, vclass, fieldName, Reflection.getCallerClass());
}
AtomicReferenceFieldUpdaterImpl(final Class<T> tclass,
final Class<V> vclass,
final String fieldName,
final Class<?> caller) {
final Field field;
final Class<?> fieldClass;
final int modifiers;
try {
field = AccessController.doPrivileged(
new PrivilegedExceptionAction<Field>() {
public Field run() throws NoSuchFieldException {
// 获取指定字段
return tclass.getDeclaredField(fieldName);
}
});
// 修饰符
modifiers = field.getModifiers();
sun.reflect.misc.ReflectUtil.ensureMemberAccess(
caller, tclass, null, modifiers);
ClassLoader cl = tclass.getClassLoader();
ClassLoader ccl = caller.getClassLoader();
if ((ccl != null) && (ccl != cl) &&
((cl == null) || !isAncestor(cl, ccl))) {
sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
}
// 字段 clazz
fieldClass = field.getType();
} catch (PrivilegedActionException pae) {
throw new RuntimeException(pae.getException());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
if (vclass != fieldClass)
throw new ClassCastException();
// 字段必须是引用类型
if (vclass.isPrimitive())
throw new IllegalArgumentException("Must be reference type");
// 字段必须有被 volatile修饰
if (!Modifier.isVolatile(modifiers))
throw new IllegalArgumentException("Must be volatile type");
// Access to protected field members is restricted to receivers only
// of the accessing class, or one of its subclasses, and the
// accessing class must in turn be a subclass (or package sibling)
// of the protected member's defining class.
// If the updater refers to a protected field of a declaring class
// outside the current package, the receiver argument will be
// narrowed to the type of the accessing class.
this.cclass = (Modifier.isProtected(modifiers) &&
tclass.isAssignableFrom(caller) &&
!isSamePackage(tclass, caller))
? caller : tclass;
this.tclass = tclass;
this.vclass = vclass;
// Unsafe操作内存, 获取字段
this.offset = U.objectFieldOffset(field);
}
上述,一方面我们可以看出 AtomicXXXFieldUpdater的玩法,字段必须要是volatile修饰的引用类型,其次通过 Unsafe去获取到了字段的偏移量
Unsfae#objectFieldOffset
/**
* Reports the location of a given field in the storage allocation of its
* class. Do not expect to perform any sort of arithmetic on this offset;
* it is just a cookie which is passed to the unsafe heap memory accessors.
*
* <p>Any given field will always have the same offset and base, and no
* two distinct fields of the same class will ever have the same offset
* and base.
*
* <p>As of 1.4.1, offsets for fields are represented as long values,
* although the Sun JVM does not use the most significant 32 bits.
* However, JVM implementations which store static fields at absolute
* addresses can use long offsets and null base pointers to express
* the field locations in a form usable by {@link #getInt(Object,long)}.
* Therefore, code which will be ported to such JVMs on 64-bit platforms
* must preserve all bits of static field offsets.
* @see #getInt(Object, long)
*/
public long objectFieldOffset(Field f) {
if (f == null) {
throw new NullPointerException();
}
return objectFieldOffset0(f);
}
private native long objectFieldOffset0(Field f);
从注释中也可以看到:不要指望对一个字段的偏移量去做些操作,以及一个类中的两个字段偏移量不可能相同
其实这里理解起来很简单,Java文件编译成字节码,字节码加载后 jvm后偏移量便是不变的,即我们可以通过对象在 jvm内存中的地址以及 offset便可以定位到字段,这块涉及到内存的操作,就去使用了 Unsafe,实际上这也是 CAS核心.
获取的 offset,在操作时只需要再去获取对象在内存中的地址,就能去操作该字段了
AtomicReferenceFieldUpdater#getAndSet
/**
* Atomically sets the field of the given object managed by this updater
* to the given value and returns the old value.
*
* @param obj An object whose field to get and set
* @param newValue the new value
* @return the previous value
*/
public V getAndSet(T obj, V newValue) {
V prev;
do {
prev = get(obj);
} while (!compareAndSet(obj, prev, newValue));
return prev;
}
public final boolean compareAndSet(T obj, V expect, V update) {
accessCheck(obj);
valueCheck(update);
return U.compareAndSetObject(obj, offset, expect, update);
}
/**
* Atomically updates Java variable to {@code x} if it is currently
* holding {@code expected}.
*
* <p>This operation has memory semantics of a {@code volatile} read
* and write. Corresponds to C11 atomic_compare_exchange_strong.
*
* @return {@code true} if successful
*/
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetObject(Object o, long offset,
Object expected,
Object x);
可以看到,Reactor中以及其它代码库、框架底层都是这么去玩的
AtomicXXXFieldUpdater玩法,相较于 AtomicReference并不会存在内存上的问题,虽然二者底层玩的都是一套,但前者通常会被设计为 public static final,其实就是基于字节码加载到 jvm后字段相较于对象内存 offset固定的特性进行操作的、
其实,上述设计还可以去优化
AbstractQueuedSynchronized
/**
* The synchronization state.
*/
private volatile int state;
// VarHandle mechanics
private static final VarHandle STATE;
private static final VarHandle HEAD;
private static final VarHandle TAIL;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
STATE = l.findVarHandle(AbstractQueuedSynchronizer.class, "state", int.class);
HEAD = l.findVarHandle(AbstractQueuedSynchronizer.class, "head", Node.class);
TAIL = l.findVarHandle(AbstractQueuedSynchronizer.class, "tail", Node.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
// Reduce the risk of rare disastrous classloading in first call to
// LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773
Class<?> ensureLoaded = LockSupport.class;
}
Java9中引入了 VarHandle,实际上就是对 AtomicXXXFieldUpdater的再迭代,使之语义上变得更加清晰明了
其实不难发现,好的代码库、框架设计中都能看到 JDK代码库的影子,JDK可以说是代码库设计的源泉,其本身也会去积极纳入设计良好的东西,所以说呀,多去看些 JDK代码对框架设计方面还是挺有帮助的
VarHandle,变量句柄,顾名思义,其实就是对变量的强引用
使用 VarHandle,能够给我们带来哪些性能上的优化?
以往我们去操作一个变量时,为了保证在多线程下其操作的正确性,我们通常会这么去做:Unsafe操作内存,Volatile保证内存操作的可见性、有序性,cmpxchg去里有 cpu提供给我们的原子指令的硬件支持,这其实也正是 AtomicXXXFieldUpdater底层会去做的,当然这可能会存在问题的:Unsafe操作内存的不安全性,以及 AtomicXXXFieldUpdater底层使用反射其实也是会消耗性能的
VarHandle的出现,替代了 Unsafe、Atomic的部分操作,可以在 JDK代码库中发现,原先很多使用 Unsafe的地方,已经去使用 VarHandle了
JDK中提供可一个工厂类去创建变量句柄:MethodHandles.Lookup#lookUp
// 用于创建变量句柄和方法句柄的工厂类
public static final
class Lookup {
...
/**
* Produces a VarHandle giving access to a non-static field {@code name}
* of type {@code type} declared in a class of type {@code recv}.
* The VarHandle's variable type is {@code type} and it has one
* coordinate type, {@code recv}.
* <p>
* Access checking is performed immediately on behalf of the lookup
* class.
* <p>
* Certain access modes of the returned VarHandle are unsupported under
* the following conditions:
* <ul>
* <li>if the field is declared {@code final}, then the write, atomic
* update, numeric atomic update, and bitwise atomic update access
* modes are unsupported.
* <li>if the field type is anything other than {@code byte},
* {@code short}, {@code char}, {@code int}, {@code long},
* {@code float}, or {@code double} then numeric atomic update
* access modes are unsupported.
* <li>if the field type is anything other than {@code boolean},
* {@code byte}, {@code short}, {@code char}, {@code int} or
* {@code long} then bitwise atomic update access modes are
* unsupported.
* </ul>
* <p>
* If the field is declared {@code volatile} then the returned VarHandle
* will override access to the field (effectively ignore the
* {@code volatile} declaration) in accordance to its specified
* access modes.
* <p>
* If the field type is {@code float} or {@code double} then numeric
* and atomic update access modes compare values using their bitwise
* representation (see {@link Float#floatToRawIntBits} and
* {@link Double#doubleToRawLongBits}, respectively).
* @apiNote
* Bitwise comparison of {@code float} values or {@code double} values,
* as performed by the numeric and atomic update access modes, differ
* from the primitive {@code ==} operator and the {@link Float#equals}
* and {@link Double#equals} methods, specifically with respect to
* comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
* Care should be taken when performing a compare and set or a compare
* and exchange operation with such values since the operation may
* unexpectedly fail.
* There are many possible NaN values that are considered to be
* {@code NaN} in Java, although no IEEE 754 floating-point operation
* provided by Java can distinguish between them. Operation failure can
* occur if the expected or witness value is a NaN value and it is
* transformed (perhaps in a platform specific manner) into another NaN
* value, and thus has a different bitwise representation (see
* {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
* details).
* The values {@code -0.0} and {@code +0.0} have different bitwise
* representations but are considered equal when using the primitive
* {@code ==} operator. Operation failure can occur if, for example, a
* numeric algorithm computes an expected value to be say {@code -0.0}
* and previously computed the witness value to be say {@code +0.0}.
* @param recv the receiver class, of type {@code R}, that declares the
* non-static field
* @param name the field's name
* @param type the field's type, of type {@code T}
* @return a VarHandle giving access to non-static fields.
* @throws NoSuchFieldException if the field does not exist
* @throws IllegalAccessException if access checking fails, or if the field is {@code static}
* @exception SecurityException if a security manager is present and it
* <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
* @throws NullPointerException if any argument is null
* @since 9
*/
// 返回 recv类中的类型为 type、字段名为 name的非静态成员
public VarHandle findVarHandle(Class<?> recv, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
// MemberName - 描述一个字段、方法引用的数据结构
MemberName getField = resolveOrFail(REF_getField, recv, name, type);
MemberName putField = resolveOrFail(REF_putField, recv, name, type);
return getFieldVarHandle(REF_getField, REF_putField, recv, getField, putField);
}
可以看到,Lookup便是通过 MemberName来去创建变量句柄的,而 MemberName就是描述一个字段、方法引用的数据结构:
/*non-public*/ final class MemberName implements Member, Cloneable {
private Class<?> clazz; // class in which the method is defined
private String name; // may be null if not yet materialized
private Object type; // may be null if not yet materialized
private int flags; // modifier bits; see reflect.Modifier
//@Injected JVM_Method* vmtarget;
//@Injected int vmindex;
private Object resolution; // if null, this guy is resolved
由 REF_getField、REF_putField,不难联想到字节码中的 getField、putField,二者之间可能存在着什么关系,这后续再来讨论
由此,我们便创建出了 VarHandle引用,之后再去基于 VarHandle提供的方法进行相关操作即可