diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/LocalCacheFactoryGenerator.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/LocalCacheFactoryGenerator.java index f9f8ad5c1e..13311ed3b7 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/LocalCacheFactoryGenerator.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/LocalCacheFactoryGenerator.java @@ -140,10 +140,10 @@ private void writeJavaFile() throws IOException { for (TypeSpec typeSpec : factoryTypes) { JavaFile.builder(getClass().getPackage().getName(), typeSpec) - .addFileComment(header, Year.now(timeZone)) - .indent(" ") - .build() - .writeTo(directory); + .addFileComment(header, Year.now(timeZone)) + .indent(" ") + .build() + .writeTo(directory); } } diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/LocalCacheSelectorCode.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/LocalCacheSelectorCode.java index 43fd50e64c..cec9f53860 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/LocalCacheSelectorCode.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/LocalCacheSelectorCode.java @@ -22,7 +22,6 @@ import java.lang.reflect.Constructor; -import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.TypeName; @@ -35,8 +34,8 @@ public final class LocalCacheSelectorCode { private LocalCacheSelectorCode() { block = CodeBlock.builder() - .addStatement("$1T sb = new $1T(\"$2N.\")", StringBuilder.class, - ((ClassName)LOCAL_CACHE_FACTORY).packageName()); + .addStatement("$1T sb = new $1T(\"$2N.\")", + StringBuilder.class, LOCAL_CACHE_FACTORY.packageName()); } private LocalCacheSelectorCode keys() { diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/NodeFactoryGenerator.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/NodeFactoryGenerator.java index 74f97abe11..e43bbfa0ed 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/NodeFactoryGenerator.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/NodeFactoryGenerator.java @@ -66,6 +66,7 @@ import com.github.benmanes.caffeine.cache.node.Finalize; import com.github.benmanes.caffeine.cache.node.NodeContext; import com.github.benmanes.caffeine.cache.node.NodeRule; +import com.google.common.base.CaseFormat; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -142,10 +143,10 @@ private void writeJavaFile() throws IOException { for (TypeSpec node : nodeTypes) { JavaFile.builder(getClass().getPackage().getName(), node) - .addFileComment(header, Year.now(timeZone)) - .indent(" ") - .build() - .writeTo(directory); + .addFileComment(header, Year.now(timeZone)) + .indent(" ") + .build() + .writeTo(directory); } } @@ -172,7 +173,16 @@ private void addClassJavaDoc() { } private void addConstants() { - Modifier[] modifiers = {Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL}; + List constants = ImmutableList.of("key", "value", "accessTime", "writeTime"); + for (String constant : constants) { + String name = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, constant); + nodeFactory.addField(FieldSpec.builder(String.class, name) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .initializer("$S", constant) + .build()); + } + + Modifier[] modifiers = { Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL }; nodeFactory.addField(FieldSpec.builder(Object.class, RETIRED_STRONG_KEY, modifiers) .initializer("new Object()") .build()); diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/Specifications.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/Specifications.java index dd860460ec..ed4beca305 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/Specifications.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/Specifications.java @@ -15,13 +15,11 @@ */ package com.github.benmanes.caffeine.cache; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.lang.ref.ReferenceQueue; -import javax.lang.model.element.Modifier; - -import com.google.common.base.CaseFormat; import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; @@ -63,10 +61,11 @@ public final class Specifications { public static final ParameterSpec valueRefQueueSpec = ParameterSpec.builder(vRefQueueType, "valueReferenceQueue").build(); - public static final TypeName UNSAFE_ACCESS = - ClassName.get("com.github.benmanes.caffeine.cache", "UnsafeAccess"); + public static final TypeName METHOD_HANDLES = ClassName.get(MethodHandles.class); + public static final TypeName LOOKUP = ClassName.get(MethodHandles.Lookup.class); + public static final TypeName VAR_HANDLE = ClassName.get(VarHandle.class); - public static final TypeName LOCAL_CACHE_FACTORY = + public static final ClassName LOCAL_CACHE_FACTORY = ClassName.get(PACKAGE_NAME, "LocalCacheFactory"); public static final ParameterizedTypeName NODE_FACTORY = ParameterizedTypeName.get( ClassName.get(PACKAGE_NAME, "NodeFactory"), kTypeVar, vTypeVar); @@ -109,20 +108,4 @@ public final class Specifications { ClassName.get(PACKAGE_NAME, "FrequencySketch"), kTypeVar); private Specifications() {} - - /** Returns the offset constant to this variable. */ - public static String offsetName(String varName) { - return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, varName) + "_OFFSET"; - } - - /** Creates a public static field with an Unsafe address offset. */ - public static FieldSpec newFieldOffset(String className, String varName) { - String fieldName = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, varName); - return FieldSpec - .builder(long.class, offsetName(varName), - Modifier.PROTECTED, Modifier.STATIC, Modifier.FINAL) - .initializer("$T.objectFieldOffset($T.class, $L.$L)", UNSAFE_ACCESS, - ClassName.bestGuess(className), LOCAL_CACHE_FACTORY, fieldName) - .build(); - } } diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddConstructors.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddConstructors.java index c0044f68bc..b9b41e4286 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddConstructors.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddConstructors.java @@ -15,11 +15,9 @@ */ package com.github.benmanes.caffeine.cache.node; -import static com.github.benmanes.caffeine.cache.Specifications.UNSAFE_ACCESS; import static com.github.benmanes.caffeine.cache.Specifications.keyRefQueueSpec; import static com.github.benmanes.caffeine.cache.Specifications.keyRefSpec; import static com.github.benmanes.caffeine.cache.Specifications.keySpec; -import static com.github.benmanes.caffeine.cache.Specifications.offsetName; import static com.github.benmanes.caffeine.cache.Specifications.valueRefQueueSpec; import static com.github.benmanes.caffeine.cache.Specifications.valueSpec; @@ -91,14 +89,14 @@ private void callSiblingConstructor() { } private void assignKeyRefAndValue() { - context.constructorByKeyRef.addStatement("$T.UNSAFE.putObject(this, $N, $N)", - UNSAFE_ACCESS, offsetName("key"), "keyReference"); + context.constructorByKeyRef.addStatement("$L.set(this, $N)", + varHandleName("key"), "keyReference"); if (isStrongValues()) { - context.constructorByKeyRef.addStatement("$T.UNSAFE.putObject(this, $N, $N)", - UNSAFE_ACCESS, offsetName("value"), "value"); + context.constructorByKeyRef.addStatement("$L.set(this, $N)", + varHandleName("value"), "value"); } else { - context.constructorByKeyRef.addStatement("$T.UNSAFE.putObject(this, $N, new $T($N, $N, $N))", - UNSAFE_ACCESS, offsetName("value"), valueReferenceType(), "keyReference", + context.constructorByKeyRef.addStatement("$L.set(this, new $T($N, $N, $N))", + varHandleName("value"), valueReferenceType(), "keyReference", "value", "valueReferenceQueue"); } } diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddExpiration.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddExpiration.java index 45b8e35b0c..5f4592f3f6 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddExpiration.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddExpiration.java @@ -16,9 +16,6 @@ package com.github.benmanes.caffeine.cache.node; import static com.github.benmanes.caffeine.cache.Specifications.NODE; -import static com.github.benmanes.caffeine.cache.Specifications.UNSAFE_ACCESS; -import static com.github.benmanes.caffeine.cache.Specifications.newFieldOffset; -import static com.github.benmanes.caffeine.cache.Specifications.offsetName; import static org.apache.commons.lang3.StringUtils.capitalize; import javax.lang.model.element.Modifier; @@ -86,23 +83,21 @@ private void addLink(String method, String varName) { private void addVariableTime(String varName) { MethodSpec getter = MethodSpec.methodBuilder("getVariableTime") .addModifiers(Modifier.PUBLIC) - .addStatement("return $T.UNSAFE.getLong(this, $N)", - UNSAFE_ACCESS, offsetName(varName)) + .addStatement("return (long) $L.get(this)", varHandleName(varName)) .returns(long.class) .build(); MethodSpec setter = MethodSpec.methodBuilder("setVariableTime") .addModifiers(Modifier.PUBLIC) .addParameter(long.class, varName) - .addStatement("$T.UNSAFE.putLong(this, $N, $N)", - UNSAFE_ACCESS, offsetName(varName), varName) + .addStatement("$L.set(this, $N)", varHandleName(varName), varName) .build(); MethodSpec cas = MethodSpec.methodBuilder("casVariableTime") .addModifiers(Modifier.PUBLIC) .addParameter(long.class, "expect") .addParameter(long.class, "update") .returns(boolean.class) - .addStatement("return ($N == $N)\n&& $T.UNSAFE.compareAndSwapLong(this, $N, $N, $N)", - varName, "expect", UNSAFE_ACCESS, offsetName(varName), "expect", "update") + .addStatement("return ($N == $N)\n&& $L.compareAndSet(this, $N, $N)", + varName, "expect", varHandleName(varName), "expect", "update") .build(); context.nodeSubtype .addMethod(getter) @@ -114,10 +109,12 @@ private void addAccessExpiration() { if (!context.generateFeatures.contains(Feature.EXPIRE_ACCESS)) { return; } - context.nodeSubtype.addField(newFieldOffset(context.className, "accessTime")) + + context.nodeSubtype .addField(long.class, "accessTime", Modifier.VOLATILE) - .addMethod(newGetter(Strength.STRONG, TypeName.LONG, "accessTime", Visibility.LAZY)) - .addMethod(newSetter(TypeName.LONG, "accessTime", Visibility.LAZY)); + .addMethod(newGetter(Strength.STRONG, TypeName.LONG, "accessTime", Visibility.PLAIN)) + .addMethod(newSetter(TypeName.LONG, "accessTime", Visibility.PLAIN)); + addVarHandle("accessTime", long.class); addTimeConstructorAssignment(context.constructorByKey, "accessTime"); addTimeConstructorAssignment(context.constructorByKeyRef, "accessTime"); } @@ -125,10 +122,11 @@ private void addAccessExpiration() { private void addWriteExpiration() { if (!Feature.useWriteTime(context.parentFeatures) && Feature.useWriteTime(context.generateFeatures)) { - context.nodeSubtype.addField(newFieldOffset(context.className, "writeTime")) + context.nodeSubtype .addField(long.class, "writeTime", Modifier.VOLATILE) - .addMethod(newGetter(Strength.STRONG, TypeName.LONG, "writeTime", Visibility.LAZY)) - .addMethod(newSetter(TypeName.LONG, "writeTime", Visibility.LAZY)); + .addMethod(newGetter(Strength.STRONG, TypeName.LONG, "writeTime", Visibility.PLAIN)) + .addMethod(newSetter(TypeName.LONG, "writeTime", Visibility.PLAIN)); + addVarHandle("writeTime", long.class); addTimeConstructorAssignment(context.constructorByKey, "writeTime"); addTimeConstructorAssignment(context.constructorByKeyRef, "writeTime"); } @@ -143,14 +141,13 @@ private void addRefreshExpiration() { .addParameter(long.class, "expect") .addParameter(long.class, "update") .returns(boolean.class) - .addStatement("return ($N == $N)\n&& $T.UNSAFE.compareAndSwapLong(this, $N, $N, $N)", - "writeTime", "expect", UNSAFE_ACCESS, offsetName("writeTime"), "expect", "update") + .addStatement("return ($N == $N)\n&& $L.compareAndSet(this, $N, $N)", + "writeTime", "expect", varHandleName("writeTime"), "expect", "update") .build()); } /** Adds a long constructor assignment. */ private void addTimeConstructorAssignment(MethodSpec.Builder constructor, String field) { - constructor.addStatement("$T.UNSAFE.putLong(this, $N, $N)", - UNSAFE_ACCESS, offsetName(field), "now"); + constructor.addStatement("$L.set(this, $N)", varHandleName(field), "now"); } } diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddHealth.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddHealth.java index 10883dcbac..ffa1c8e4cb 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddHealth.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddHealth.java @@ -19,8 +19,6 @@ import static com.github.benmanes.caffeine.cache.Specifications.DEAD_WEAK_KEY; import static com.github.benmanes.caffeine.cache.Specifications.RETIRED_STRONG_KEY; import static com.github.benmanes.caffeine.cache.Specifications.RETIRED_WEAK_KEY; -import static com.github.benmanes.caffeine.cache.Specifications.UNSAFE_ACCESS; -import static com.github.benmanes.caffeine.cache.Specifications.offsetName; import java.lang.ref.Reference; @@ -76,13 +74,12 @@ private void addState(String checkName, String actionName, String arg, boolean f // Set the value to null only when dead, as otherwise the explicit removal of an expired async // value will be notified as explicit rather than expired due to the isComputingAsync() check if (finalized) { - action.addStatement("$T.UNSAFE.putObject(this, $N, null)", - UNSAFE_ACCESS, offsetName("value")); + action.addStatement("$L.set(this, null)", varHandleName("value")); } } else { action.addStatement("(($T) getValueReference()).clear()", Reference.class); } - action.addStatement("$T.UNSAFE.putObject(this, $N, $N)", UNSAFE_ACCESS, offsetName("key"), arg); + action.addStatement("$L.set(this, $N)", varHandleName("key"), arg); context.nodeSubtype.addMethod(action.build()); } } diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddKey.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddKey.java index 83177bf2f2..f2d7600dd9 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddKey.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddKey.java @@ -16,7 +16,6 @@ package com.github.benmanes.caffeine.cache.node; import static com.github.benmanes.caffeine.cache.Specifications.kTypeVar; -import static com.github.benmanes.caffeine.cache.Specifications.newFieldOffset; import javax.lang.model.element.Modifier; @@ -38,10 +37,10 @@ protected boolean applies() { @Override protected void execute() { context.nodeSubtype - .addField(newFieldOffset(context.className, "key")) .addField(newKeyField()) - .addMethod(newGetter(keyStrength(), kTypeVar, "key", Visibility.LAZY)) + .addMethod(newGetter(keyStrength(), kTypeVar, "key", Visibility.PLAIN)) .addMethod(newGetRef("key")); + addVarHandle("key", Object.class); } private FieldSpec newKeyField() { diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddValue.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddValue.java index 9298667655..0a14faa9e5 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddValue.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/AddValue.java @@ -15,9 +15,6 @@ */ package com.github.benmanes.caffeine.cache.node; -import static com.github.benmanes.caffeine.cache.Specifications.UNSAFE_ACCESS; -import static com.github.benmanes.caffeine.cache.Specifications.newFieldOffset; -import static com.github.benmanes.caffeine.cache.Specifications.offsetName; import static com.github.benmanes.caffeine.cache.Specifications.vRefQueueType; import static com.github.benmanes.caffeine.cache.Specifications.vTypeVar; @@ -45,12 +42,12 @@ protected boolean applies() { @Override protected void execute() { context.nodeSubtype - .addField(newFieldOffset(context.className, "value")) .addField(newValueField()) - .addMethod(newGetter(valueStrength(), vTypeVar, "value", Visibility.LAZY)) + .addMethod(newGetter(valueStrength(), vTypeVar, "value", Visibility.PLAIN)) .addMethod(newGetRef("value")) .addMethod(makeSetValue()) .addMethod(makeContainsValue()); + addVarHandle("value", Object.class); } private FieldSpec newValueField() { @@ -68,12 +65,11 @@ private MethodSpec makeSetValue() { .addParameter(vRefQueueType, "referenceQueue"); if (isStrongValues()) { - setter.addStatement("$T.UNSAFE.putObject(this, $N, $N)", - UNSAFE_ACCESS, offsetName("value"), "value"); + setter.addStatement("$L.set(this, $N)", varHandleName("value"), "value"); } else { setter.addStatement("(($T) getValueReference()).clear()", Reference.class); - setter.addStatement("$T.UNSAFE.putObject(this, $N, new $T($L, $N, referenceQueue))", - UNSAFE_ACCESS, offsetName("value"), valueReferenceType(), "getKeyReference()", "value"); + setter.addStatement("$L.set(this, new $T($L, $N, referenceQueue))", + varHandleName("value"), valueReferenceType(), "getKeyReference()", "value"); } return setter.build(); diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/Finalize.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/Finalize.java index b48d9d18f8..6ec4c48386 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/Finalize.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/Finalize.java @@ -15,6 +15,11 @@ */ package com.github.benmanes.caffeine.cache.node; +import static com.github.benmanes.caffeine.cache.Specifications.LOOKUP; +import static com.github.benmanes.caffeine.cache.Specifications.METHOD_HANDLES; + +import com.squareup.javapoet.CodeBlock; + /** * Finishes construction of the node. * @@ -33,5 +38,23 @@ protected void execute() { .addMethod(context.constructorDefault.build()) .addMethod(context.constructorByKey.build()) .addMethod(context.constructorByKeyRef.build()); + addStaticBlock(); + } + + public void addStaticBlock() { + if (context.varHandles.isEmpty()) { + return; + } + var codeBlock = CodeBlock.builder() + .addStatement("$T lookup = $T.lookup()", LOOKUP, METHOD_HANDLES) + .beginControlFlow("try"); + for (var varHandle : context.varHandles) { + varHandle.accept(codeBlock); + } + codeBlock + .nextControlFlow("catch ($T e)", ReflectiveOperationException.class) + .addStatement("throw new ExceptionInInitializerError(e)") + .endControlFlow(); + context.nodeSubtype.addStaticBlock(codeBlock.build()); } } diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/NodeContext.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/NodeContext.java index f1357250c8..9cb4347cc0 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/NodeContext.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/NodeContext.java @@ -17,11 +17,15 @@ import static java.util.Objects.requireNonNull; +import java.util.ArrayList; +import java.util.List; import java.util.Set; +import java.util.function.Consumer; import javax.lang.model.element.Modifier; import com.github.benmanes.caffeine.cache.Feature; +import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; @@ -35,6 +39,7 @@ public final class NodeContext { public final TypeName superClass; public final Set parentFeatures; public final Set generateFeatures; + public final List> varHandles; public TypeSpec.Builder nodeSubtype; public MethodSpec.Builder constructorByKey; @@ -45,6 +50,7 @@ public final class NodeContext { public NodeContext(TypeName superClass, String className, boolean isFinal, Set parentFeatures, Set generateFeatures) { this.isFinal = isFinal; + this.varHandles = new ArrayList<>(); this.className = requireNonNull(className); this.superClass = requireNonNull(superClass); this.parentFeatures = requireNonNull(parentFeatures); diff --git a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/NodeRule.java b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/NodeRule.java index 3c678904f0..eb88805830 100644 --- a/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/NodeRule.java +++ b/caffeine/src/javaPoet/java/com/github/benmanes/caffeine/cache/node/NodeRule.java @@ -15,20 +15,25 @@ */ package com.github.benmanes.caffeine.cache.node; +import static com.github.benmanes.caffeine.cache.Specifications.NODE_FACTORY; import static com.github.benmanes.caffeine.cache.Specifications.PACKAGE_NAME; -import static com.github.benmanes.caffeine.cache.Specifications.UNSAFE_ACCESS; import static com.github.benmanes.caffeine.cache.Specifications.kTypeVar; -import static com.github.benmanes.caffeine.cache.Specifications.offsetName; import static com.github.benmanes.caffeine.cache.Specifications.vTypeVar; import static com.google.common.base.Preconditions.checkState; import static org.apache.commons.lang3.StringUtils.capitalize; +import java.lang.invoke.VarHandle; import java.lang.ref.Reference; import java.util.function.Consumer; +import javax.lang.model.element.Modifier; + import com.github.benmanes.caffeine.cache.Feature; +import com.google.common.base.CaseFormat; import com.google.common.collect.Iterables; import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; @@ -94,13 +99,29 @@ protected TypeName valueReferenceType() { return ParameterizedTypeName.get(ClassName.get(PACKAGE_NAME + ".References", clazz), vTypeVar); } + /** Returns the name of the VarHandle to this variable. */ + protected String varHandleName(String varName) { + return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, varName); + } + + /** Creates a VarHandle to the instance field. */ + public void addVarHandle(String varName, Class type) { + String fieldName = varHandleName(varName); + context.nodeSubtype.addField(FieldSpec.builder(VarHandle.class, fieldName, + Modifier.PROTECTED, Modifier.STATIC, Modifier.FINAL).build()); + Consumer statement = builder -> builder + .addStatement("$L = lookup.findVarHandle($T.class, $L.$L, $T.class)", fieldName, + ClassName.bestGuess(context.className), NODE_FACTORY.rawType.simpleName(), + fieldName, type); + context.varHandles.add(statement); + } + /** Creates an accessor that returns the reference. */ protected final MethodSpec newGetRef(String varName) { MethodSpec.Builder getter = MethodSpec.methodBuilder("get" + capitalize(varName) + "Reference") .addModifiers(context.publicFinalModifiers()) .returns(Object.class); - getter.addStatement("return $T.UNSAFE.getObject(this, $N)", - UNSAFE_ACCESS, offsetName(varName)); + getter.addStatement("return $L.get(this)", varHandleName(varName)); return getter.build(); } @@ -110,28 +131,21 @@ protected final MethodSpec newGetter(Strength strength, TypeName varType, MethodSpec.Builder getter = MethodSpec.methodBuilder("get" + capitalize(varName)) .addModifiers(context.publicFinalModifiers()) .returns(varType); - String type; - if (varType.isPrimitive()) { - type = varType.equals(TypeName.INT) ? "Int" : "Long"; - } else { - type = "Object"; - } if (strength == Strength.STRONG) { - if (visibility.isRelaxed) { + if (visibility.isPlain) { if (varType.isPrimitive()) { - getter.addStatement("return $T.UNSAFE.get$N(this, $N)", - UNSAFE_ACCESS, type, offsetName(varName)); + getter.addStatement("return ($L) $L.get(this)", + varType.toString(), varHandleName(varName)); } else { - getter.addStatement("return ($T) $T.UNSAFE.get$N(this, $N)", - varType, UNSAFE_ACCESS, type, offsetName(varName)); + getter.addStatement("return ($T) $L.get(this)", varType, varHandleName(varName)); } } else { getter.addStatement("return $N", varName); } } else { - if (visibility.isRelaxed) { - getter.addStatement("return (($T<$T>) $T.UNSAFE.get$N(this, $N)).get()", - Reference.class, varType, UNSAFE_ACCESS, type, offsetName(varName)); + if (visibility.isPlain) { + getter.addStatement("return (($T<$T>) $L.get(this)).get()", + Reference.class, varType, varHandleName(varName)); } else { getter.addStatement("return $N.get()", varName); } @@ -142,22 +156,14 @@ protected final MethodSpec newGetter(Strength strength, TypeName varType, /** Creates a mutator to the variable. */ protected final MethodSpec newSetter(TypeName varType, String varName, Visibility visibility) { String methodName = "set" + Character.toUpperCase(varName.charAt(0)) + varName.substring(1); - String type; - if (varType.isPrimitive()) { - type = varType.equals(TypeName.INT) ? "Int" : "Long"; - } else { - type = "Object"; - } MethodSpec.Builder setter = MethodSpec.methodBuilder(methodName) .addModifiers(context.publicFinalModifiers()) .addParameter(varType, varName); - if (visibility.isRelaxed) { - setter.addStatement("$T.UNSAFE.put$L(this, $N, $N)", - UNSAFE_ACCESS, type, offsetName(varName), varName); + if (visibility.isPlain) { + setter.addStatement("$L.set(this, $N)", varHandleName(varName), varName); } else { setter.addStatement("this.$N = $N", varName, varName); } - return setter.build(); } @@ -175,12 +181,12 @@ protected enum Strength { } protected enum Visibility { - IMMEDIATE(false), LAZY(true); + IMMEDIATE(false), PLAIN(true); - final boolean isRelaxed; + final boolean isPlain; Visibility(boolean mode) { - this.isRelaxed = mode; + this.isPlain = mode; } } } diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedBuffer.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedBuffer.java index 6d959c30fc..062474a767 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedBuffer.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedBuffer.java @@ -15,6 +15,8 @@ */ package com.github.benmanes.caffeine.cache; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.Consumer; @@ -136,14 +138,7 @@ abstract static class PadReadCounter { /** Enforces a memory layout to avoid false sharing by padding the read count. */ abstract static class ReadCounterRef extends PadReadCounter { - static final long READ_OFFSET = - UnsafeAccess.objectFieldOffset(ReadCounterRef.class, "readCounter"); - volatile long readCounter; - - void lazySetReadCounter(long count) { - UnsafeAccess.UNSAFE.putOrderedLong(this, READ_OFFSET, count); - } } abstract static class PadWriteCounter extends ReadCounterRef { @@ -166,21 +161,34 @@ abstract static class PadWriteCounter extends ReadCounterRef { /** Enforces a memory layout to avoid false sharing by padding the write count. */ abstract static class ReadAndWriteCounterRef extends PadWriteCounter { - static final long WRITE_OFFSET = - UnsafeAccess.objectFieldOffset(ReadAndWriteCounterRef.class, "writeCounter"); + static final VarHandle READ, WRITE; volatile long writeCounter; ReadAndWriteCounterRef() { - UnsafeAccess.UNSAFE.putOrderedLong(this, WRITE_OFFSET, 1); + WRITE.setOpaque(this, 1); + } + + void lazySetReadCounter(long count) { + READ.setOpaque(this, count); } long relaxedWriteCounter() { - return UnsafeAccess.UNSAFE.getLong(this, WRITE_OFFSET); + return (long) WRITE.get(this); } boolean casWriteCounter(long expect, long update) { - return UnsafeAccess.UNSAFE.compareAndSwapLong(this, WRITE_OFFSET, expect, update); + return WRITE.compareAndSet(this, expect, update); + } + + static { + var lookup = MethodHandles.lookup(); + try { + READ = lookup.findVarHandle(ReadCounterRef.class, "readCounter", long.class); + WRITE = lookup.findVarHandle(ReadAndWriteCounterRef.class, "writeCounter", long.class); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } } } } diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java index 66c59d7f50..e7b9996669 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java @@ -31,6 +31,8 @@ import java.io.Serializable; import java.lang.System.Logger; import java.lang.System.Logger.Level; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; @@ -3959,8 +3961,7 @@ abstract static class PadDrainStatus extends AbstractMap { /** Enforces a memory layout to avoid false sharing by padding the drain status. */ abstract static class DrainStatusRef extends PadDrainStatus { - static final long DRAIN_STATUS_OFFSET = - UnsafeAccess.objectFieldOffset(DrainStatusRef.class, "drainStatus"); + static final VarHandle DRAIN_STATUS; /** A drain is not taking place. */ static final int IDLE = 0; @@ -3994,15 +3995,24 @@ boolean shouldDrainBuffers(boolean delayable) { } int drainStatus() { - return UnsafeAccess.UNSAFE.getInt(this, DRAIN_STATUS_OFFSET); + return (int) DRAIN_STATUS.get(this); } void lazySetDrainStatus(int drainStatus) { - UnsafeAccess.UNSAFE.putOrderedInt(this, DRAIN_STATUS_OFFSET, drainStatus); + DRAIN_STATUS.setOpaque(this, drainStatus); } boolean casDrainStatus(int expect, int update) { - return UnsafeAccess.UNSAFE.compareAndSwapInt(this, DRAIN_STATUS_OFFSET, expect, update); + return DRAIN_STATUS.compareAndSet(this, expect, update); + } + + static { + try { + DRAIN_STATUS = MethodHandles.lookup() + .findVarHandle(DrainStatusRef.class, "drainStatus", int.class); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } } } }