JDK 9新特性之VarHandle

2024-10-01 00:38:22 浏览数 (1)

概述

在ConcurrentSkipListMap里看到VarHandle,记录一下学习笔记。

VarHandle,变量句柄,是新的原子访问属性规范,JDK8以前都是通过sun.misc.Unsafe实现原子属性访问。见名知意,Unsafe是不安全API,理解不透彻使用不正确,会有意想不到的问题。从JDK9开始,会尽可能使用VarHandle代替Unsafe,实际上VarHandle内部有几个内存屏障相关的方法还是基于Unsafe。可以说,Unsafe是更底层的API,建议使用VarHandle而不是Unsafe。

VarHandle提供一系列标准的内存屏障操作,用于更加细粒度的内存排序控制,可以针对特定的字段或变量使用VarHandle,而不需要对整个对象或代码块进行同步。在安全性、可用性、性能上都要优于现有的API。

VarHandle可与各种类型的变量一起使用,包括基本数据类型、引用类型、数组等,支持在不同访问模式下对这些类型变量的访问,包括简单的read/write访问,volatile read/write访问,和CAS(compare-and-set)等。这使得VarHandle在处理不同类型的并发问题时都非常灵活和方便。

体系

VarHandle是一个抽象基类,实现类有:

  • IndirectVarHandle
  • LazyInitializingVarHandle

针对不同的数据类型,如8种基础数据类型(int、byte、short、char、boolean、long、float、double),VarHandle都提供支持:

  • VarHandleBytes
  • VarHandleChars
  • VarHandleShorts
  • VarHandleBooleans
  • VarHandleInts
  • VarHandleLongs
  • VarHandleFloats
  • VarHandleDoubles
  • VarHandleReferences
  • VarHandleByteArray

VarHandleByteArrayBase也是一个抽象基类,其实现类有:

  • VarHandleByteArrayAsChars
  • VarHandleByteArrayAsDoubles
  • VarHandleByteArrayAsFloats
  • VarHandleByteArrayAsInts
  • VarHandleByteArrayAsLongs
  • VarHandleByteArrayAsShorts

且这些实现类基于某种机制(待调研)自动生成的,类结构一样一样:

代码语言:java复制
// -- This file was mechanically generated: Do not edit! -- //
final class VarHandleByteArrayAsChars extends VarHandleByteArrayBase {
	static final JavaNioAccess NIO_ACCESS = SharedSecrets.getJavaNioAccess();
	
	static final int ALIGN = Character.BYTES - 1;
	
	static final ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess();
	
	@ForceInline
	static char convEndian(boolean big, char n) {
		return big == BE ? n : Character.reverseBytes(n);
	}
	
	static abstract sealed class ByteArrayViewVarHandle extends VarHandle {
	}
	
	static final class ArrayHandle extends ByteArrayViewVarHandle {
		// 省略方法若干,下同
		static final VarForm FORM = new VarForm(ArrayHandle.class, byte[].class, char.class, int.class);
	}
	
	static final class ByteBufferHandle extends ByteArrayViewVarHandle {
		static final VarForm FORM = new VarForm(ByteBufferHandle.class, ByteBuffer.class, char.class, int.class);
	}
}

源码还是涉及到很多API,如ScopedMemoryAccess、VarForm、SharedSecrets、JavaNioAccess、Unsafe、MemorySegment。

JDK源码真是博大精深啊,简历上写【精通Java】的人脸皮真厚,我也是。

创建VarHandle

创建VarHandle实例之前,可以先获取MethodHandles.Lookup实例:

  • MethodHandles.Lookup:是一个工厂类,用于创建方法句柄(Method Handles),这些句柄可用于访问类的字段、方法和构造函数。Lookup对象的权限和功能取决于它的创建上下文(通常是某个类),以及相应的访问权限。提供对不同访问级别成员的访问,包括:public、package、protected、private
  • MethodHandles.publicLookup:获取一个可访问任何类的公共成员的Lookup对象。它没有包私有、受保护或私有成员的访问权限
  • MethodHandles.privateLookupIn():获取一个对指定类的私有成员的访问权限的Lookup实例,通常用于跨模块访问的场景中,特别是在模块化系统中,允许安全地访问其他模块中的私有成员

基于MethodHandles.Lookup,创建VarHandle的方法有:

  • findVarHandle:用于创建对象中非静态字段的VarHandle。接收参数有三个,分别是接收者的class对象,字段名称和类型
  • findStaticVarHandle:用于创建对象中静态字段的VarHandle。接收参数与findVarHandle一致
  • unreflectVarHandle:通过反射字段Field创建VarHandle

直接使用MethodHandles,创建VarHandle的方法有:

  • MethodHandles.arrayElementVarHandle(int[].class):获取管理数组的Varhandle
  • MethodHandles.byteArrayViewVarHandle
  • byteBufferViewVarHandle:
  • filterValue
  • collectCoordinates:实际上调用VarHandles提供的同名方法,下同
  • insertCoordinates
  • filterCoordinates
  • permuteCoordinates
  • dropCoordinates

VarHandles是JDK内部使用的,不对外开放的工具类。

访问类别

参考VarHandle.AccessType源码:

代码语言:java复制
enum AccessType {
	GET(Object.class),
	SET(void.class),
	COMPARE_AND_SET(boolean.class),
	COMPARE_AND_EXCHANGE(Object.class),
	GET_AND_UPDATE(Object.class);
}
  • GET get getVolatile:相当于获取后加LoadLoad LoadStore getAcquire getOpaque
  • SET set setVolatile setRelease setOpaque
  • COMPARE_AND_SET compareAndSet weakCompareAndSetPlain weakCompareAndSet weakCompareAndSetAcquire weakCompareAndSetRelease
  • COMPARE_AND_EXCHANGE compareAndExchange compareAndExchangeAcquire compareAndExchangeRelease
  • GET_AND_UPDATE getAndAdd getAndAddAcquire getAndAddRelease getAndBitwiseOr getAndBitwiseOrRelease getAndBitwiseOrAcquire getAndBitwiseAnd getAndBitwiseAndRelease getAndBitwiseAndAcquire getAndBitwiseXor getAndBitwiseXorRelease getAndBitwiseXorAcquire

访问模式

参考VarHandle.AccessMode源码:

代码语言:java复制
public enum AccessMode {
	// 省略引用AccessType
	GET("get", GET),
	SET("set", SET),
	GET_VOLATILE("getVolatile", GET),
	SET_VOLATILE("setVolatile", SET),
	GET_ACQUIRE("getAcquire", GET),
	SET_RELEASE("setRelease", SET),
	GET_OPAQUE("getOpaque", GET),
	SET_OPAQUE("setOpaque", SET),
	COMPARE_AND_SET("compareAndSet", COMPARE_AND_SET),
	COMPARE_AND_EXCHANGE("compareAndExchange", COMPARE_AND_EXCHANGE),
	COMPARE_AND_EXCHANGE_ACQUIRE("compareAndExchangeAcquire", COMPARE_AND_EXCHANGE),
	COMPARE_AND_EXCHANGE_RELEASE("compareAndExchangeRelease", COMPARE_AND_EXCHANGE),
	WEAK_COMPARE_AND_SET_PLAIN("weakCompareAndSetPlain", COMPARE_AND_SET),
	WEAK_COMPARE_AND_SET("weakCompareAndSet", COMPARE_AND_SET),
	WEAK_COMPARE_AND_SET_ACQUIRE("weakCompareAndSetAcquire", COMPARE_AND_SET),
	WEAK_COMPARE_AND_SET_RELEASE("weakCompareAndSetRelease", COMPARE_AND_SET),
	GET_AND_SET("getAndSet", GET_AND_UPDATE),
	GET_AND_SET_ACQUIRE("getAndSetAcquire", GET_AND_UPDATE),
	GET_AND_SET_RELEASE("getAndSetRelease", GET_AND_UPDATE),
	GET_AND_ADD("getAndAdd", GET_AND_UPDATE),
	GET_AND_ADD_ACQUIRE("getAndAddAcquire", GET_AND_UPDATE),
	GET_AND_ADD_RELEASE("getAndAddRelease", GET_AND_UPDATE),
	GET_AND_BITWISE_OR("getAndBitwiseOr", GET_AND_UPDATE),
	GET_AND_BITWISE_OR_RELEASE("getAndBitwiseOrRelease", GET_AND_UPDATE),
	GET_AND_BITWISE_OR_ACQUIRE("getAndBitwiseOrAcquire", GET_AND_UPDATE),
	GET_AND_BITWISE_AND("getAndBitwiseAnd", GET_AND_UPDATE),
	GET_AND_BITWISE_AND_RELEASE("getAndBitwiseAndRelease", GET_AND_UPDATE),
	GET_AND_BITWISE_AND_ACQUIRE("getAndBitwiseAndAcquire", GET_AND_UPDATE),
	GET_AND_BITWISE_XOR("getAndBitwiseXor", GET_AND_UPDATE),
	GET_AND_BITWISE_XOR_RELEASE("getAndBitwiseXorRelease", GET_AND_UPDATE),
	GET_AND_BITWISE_XOR_ACQUIRE("getAndBitwiseXorAcquire", GET_AND_UPDATE),
	;

	private final String methodName;
	private final AccessType at;
}

有4种:

  • plain:普通访问,无方法后缀,不保证内存可见性及执行顺序
  • opaque:保证执行顺序,不保证内存可见性
  • acquire/release:保证执行顺序,setRelease确保前面的load和store不会被重排序到后面,但不确保后面的load和store重排序到前面;getAcquire确保后面的load和store不会被重排序到前面,但不确保前面的load和store被重排序。
  • volatile:保证执行顺序,且保证变量不会被重排

内存屏障

5个方法均为静态方法,看源码,实际上还是调用Unsafe。

方法名

方法描述

fullFence

保证方法调用之前的所有读写操作不会被方法后的读写操作重排

acquireFence

保证方法调用之前的所有读操作不会被方法后的读写操作重排

releaseFence

保证方法调用之前的所有读写操作不会被方法后的写操作重排

loadLoadFence

保证方法调用之前的所有读操作不会被方法后的读操作重排

storeStoreFence

保证方法调用之前的所有写操不会被方法后的写操作重排

实战

基本使用

代码语言:java复制
@Slf4j
public class VarHandleDemo {
	private int x; // 共享变量
	private static VarHandle X;

	static {
		try {
			// 初始化VarHandle:需指定要保护的变量,某个类中的 共享变量名名 共享变量类型。
			// 注意到这里int基础类型也可以获取到class类型
			X = MethodHandles.lookup().findVarHandle(VarHandleDemo.class, "x", int.class);
		} catch (NoSuchFieldException | IllegalAccessException e) {
			log.error("fail: {}", e.getMessage());
		}
	}

	public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
		VarHandleDemo obj = new VarHandleDemo();
		X.set(obj, 10);
		Object o = X.get(obj);
		System.out.println(o);
	}
}

高效反射

MethodHandles.Lookup等三个方法可用于反射和动态访问,能够高效、安全、精细地控制对类成员的访问。

代码语言:java复制
public class VarHandleDemo {
	public int publicVar = 1;
	protected int protectedVar = 2;
	private int privateVar = 3;
	public int[] arrayData = new int[]{1, 2, 3};
	
	@Override
	public String toString() {
		return "VarHandleDemo {"   "publicVar="   publicVar   ", protectedVar="   protectedVar   ", privateVar="   privateVar   ", arrayData="   Arrays.toString(arrayData)   '}';
	}
	
	private static void protectedDemo() throws NoSuchFieldException, IllegalAccessException {
		VarHandleDemo demo = new VarHandleDemo();
		VarHandle varHandle = MethodHandles.privateLookupIn(VarHandleDemo.class, MethodHandles.lookup()).findVarHandle(VarHandleDemo.class, "protectedVar", int.class);
		VarHandle varHandle0 = MethodHandles.privateLookupIn(VarHandleDemo.class, MethodHandles.lookup()).findVarHandle(VarHandleDemo.class, "publicVar", int.class);
		// publicLookup 访问 private 属性,报错:IllegalAccessException: member is private
		// VarHandle varHandle1 = MethodHandles.publicLookup().in(VarHandleDemo.class).findVarHandle(VarHandleDemo.class, "privateVar", int.class);
		// publicLookup 访问 protected 属性,报错:IllegalAccessException: member is protected
		// VarHandle varHandle2 = MethodHandles.publicLookup().in(VarHandleDemo.class).findVarHandle(VarHandleDemo.class, "protectedVar", int.class);
	    varHandle0.set(demo, 22);
	    System.out.println(demo);
	}


	
	public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
		protectedDemo();
		arrayDemo();
	}
}

CAS

简单示例:

代码语言:java复制
private static void arrayDemo() {
	VarHandleDemo demo = new VarHandleDemo();
	// int数组class
	VarHandle arrayVarHandle = MethodHandles.arrayElementVarHandle(int[].class);
	// 第0个索引,如果是1,则更新为11
	arrayVarHandle.compareAndSet(demo.arrayData, 0, 1, 11);
	// 第1个索引,如果是3(本来是2),则更新为22(不会更新)
	arrayVarHandle.compareAndSet(demo.arrayData, 1, 3, 22);
	System.out.println(demo);
}

按位更新

内存屏障

高级volatile

参考

  • VarHandle:Java9中保证变量读写可见性、有序性、原子性利器

0 人点赞