Une utilisation rare de WeakReference?
J'ai une classe dont les instances sont initialisées et utilisées par le flatform sous-jacent.
class MyAttributeConverter implements AttributeConverter<XX, YY> {
public YY convertToDatabaseColumn(XX attribute) { return null; }
public XX convertToEntityAttribute(YY dbData) { return null; }
}
Il n'y a rien de mal et j'ai pensé que je devais ajouter des méthodes statiques pour être utilisées comme références de méthode.
private static MyAttributeConverter instance;
// just a lazy-initialization;
// no synchronization is required;
// multiple instantiation is not a problem;
private static MyAttributeConverter instance() {
if (instance == null) {
instance = new MyAttributeConverter();
}
return instance;
}
// do as MyAttributeConverter::toDatabaseColumn(xx)
public static YY toDatabaseColumn(XX attribute) {
return instance().convertToDatabaseColumn(attribute);
}
public static XX toEntityAttribute(YY dbData) {
return instance().convertToEntityAttribute(attribute);
}
Toujours rien ne semble faux (je crois) et je n'aime pas la instancepersistance de la classe et c'est pourquoi j'essaie de faire ça.
private static WeakReference<MyAttributeConverter> reference;
public static <R> R applyInstance(Function<? super MyAttributeConverter, ? extends R> function) {
MyAttributeConverter referent;
if (reference == null) {
referent = new MyAttributeConverter();
refernce = new WeakReference<>(referent);
return applyInstance(function);
}
referent = reference.get();
if (referent == null) {
referent = new MyAttributeConverter();
refernce = new WeakReference<>(referent);
return applyInstance(function);
}
return function.apply(referent); // @@?
}
Je ne sais même pas comment tester ce code. Et je suis désolé pour mes questions qui peuvent être un peu vagues.
- Est-ce une approche (bonne / mauvaise)?
- Y a-t-il une chance que l'
reference.get()intérieur de l'function.applyidiome puisse êtrenull? - Y a-t-il un risque qu'il y ait des problèmes tels qu'une fuite de mémoire?
- Dois-je me fier
SoftReferenceplutôt queWeakReference?
Je vous remercie.
Réponses
Notez qu'une méthode comme
// multiple instantiation is not a problem;
private static MyAttributeConverter instance() {
if (instance == null) {
instance = new MyAttributeConverter();
}
return instance;
}
n'est pas thread-safe, car il supporte deux lectures du instancechamp; chacun d'eux peut percevoir ou non les mises à jour effectuées par d'autres threads. Cela implique que la première lecture instance == nullpeut percevoir une valeur plus récente écrite par un autre thread alors que la seconde return instance;peut être évaluée à la valeur précédente, c'est-à-dire null. Ainsi, cette méthode peut retourner nulllorsque plusieurs threads l'exécutent simultanément. C'est un cas rare, mais cette méthode n'est pas sûre. Vous auriez besoin d'une variable locale pour vous assurer que le test et l'instruction return utilisent la même valeur.
// multiple instantiation is not a problem;
private static MyAttributeConverter instance() {
MyAttributeConverter current = instance;
if (current == null) {
instance = current = new MyAttributeConverter();
}
return current;
}
Ceci n'est toujours sûr que lorsqu'il MyAttributeConverterest immuable en utilisant uniquement des finalchamps. Sinon, un thread peut renvoyer une instance créée par un autre thread dans un état incomplètement construit.
Vous pouvez utiliser le moyen simple de le rendre sûr sans ces contraintes:
private static final MyAttributeConverter instance = new MyAttributeConverter();
private static MyAttributeConverter instance() {
return instance;
}
Cela reste paresseux car l'initialisation de classe ne se produit que sur l' un des déclencheurs spécifiés , c'est-à-dire la première invocation de la méthode instance().
Votre utilisation de WeakReferenceest sujette aux mêmes problèmes. En outre, la raison pour laquelle vous avez recours à un appel récursif de votre méthode à deux points où vous avez déjà l'argument requis dans une variable locale n'est pas claire.
Une mise en œuvre correcte peut être beaucoup plus simple:
private static WeakReference<MyAttributeConverter> reference;
public static <R> R applyInstance(
Function<? super MyAttributeConverter, ? extends R> function) {
WeakReference<MyAttributeConverter> r = reference;
MyAttributeConverter referent = r != null? r.get(): null;
if (referent == null) {
referent = new MyAttributeConverter();
reference = new WeakReference<>(referent);
}
return function.apply(referent);
}
Mais avant de l'utiliser, vous devez reconsidérer si le code compliqué en vaut la peine. Le fait que vous acceptiez la nécessité de reconstruire l'objet lorsqu'il a été ramassé, voire de construire potentiellement plusieurs instances sur des appels simultanés, suggère que vous savez que la construction sera bon marché. Lorsque la construction est bon marché, vous n'avez probablement pas du tout besoin d'en mettre en cache une instance.
Considérez simplement
public static <R> R applyInstance(
Function<? super MyAttributeConverter, ? extends R> function) {
return function.apply(new MyAttributeConverter());
}
Cela vaut au moins la peine d'essayer, de mesurer les performances de l'application et de la comparer aux autres approches.
D'un autre côté, il ne semble pas que l'instance occupait une quantité importante de mémoire ni contenait des ressources non mémoire. Sinon, vous étiez plus inquiet de la possibilité que plusieurs instances volent. Donc, l'autre variante qui vaut la peine d'être essayée et comparée est celle illustrée ci-dessus en utilisant un static finalchamp avec une initialisation de classe paresseuse et aucune opportunité de ramasser ce petit objet.
Une dernière clarification. Tu as demandé
Y a-t-il une chance que l'
reference.get()intérieur de l'function.applyidiome puisse êtrenull?
Puisqu'il n'y a pas d' reference.get()invocation dans l'évaluation de function.apply, il n'y a aucune chance qu'une telle invocation puisse être évaluée nullà ce stade. La fonction reçoit une référence forte et puisque le code appelant garantit que cette référence forte ne l'est pas null, elle ne le deviendra jamais nulllors de l'appel de la applyméthode.
En général, le garbage collector ne modifiera jamais l'état de l'application de telle sorte que le code utilisant des références fortes remarquera une différence (en laissant de côté la disponibilité de plus de mémoire).
Mais puisque vous avez posé des questions spécifiquement sur reference.get(), un garbage collector peut collecter un objet après sa dernière utilisation , indépendamment des exécutions de méthode ou des étendues locales . Ainsi, le référent pourrait être collecté lors de l'exécution de la applyméthode lorsque cette méthode n'utilise plus l'objet. Les optimisations d'exécution peuvent permettre que cela se produise plus tôt que vous ne le pensez en regardant le code source , car ce qui peut ressembler à une utilisation d'objet (par exemple, un champ lu) peut ne pas utiliser l'objet à l'exécution (par exemple parce que cette valeur est déjà contenue dans un Registre CPU, éliminant le besoin d'accéder à la mémoire de l'objet). Comme dit, tout cela sans modifier le comportement de la méthode.
Donc, un hypothétique reference.get()lors de l'exécution de la applyméthode pourrait en principe s'évaluer null, mais il n'y a aucune raison de s'inquiéter, comme dit, le comportement de la applyméthode ne change pas. La machine virtuelle Java conservera la mémoire de l'objet aussi longtemps que nécessaire pour garantir l'exécution correcte de la méthode.
Mais cette explication était juste pour l'exhaustivité. Comme indiqué, vous ne devez pas utiliser de références faibles ou souples pour les objets ne contenant pas de ressources coûteuses.