Un uso raro di WeakReference?
Ho una classe le cui istanze sono inizializzate e utilizzate dal flatform sottostante.
class MyAttributeConverter implements AttributeConverter<XX, YY> {
public YY convertToDatabaseColumn(XX attribute) { return null; }
public XX convertToEntityAttribute(YY dbData) { return null; }
}
Non c'è niente di sbagliato e ho pensato di dover aggiungere alcuni metodi statici da utilizzare come riferimenti ai metodi.
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);
}
Ancora niente sembra sbagliato (credo) e non mi piace il instancepersistere con la classe ed è per questo che sto cercando di farlo.
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); // @@?
}
Fondamentalmente non so nemmeno come testare questo codice. E mi dispiace per le mie domande che potrebbero essere alquanto vaghe.
- È un approccio (giusto / sbagliato)?
- C'è qualche possibilità che
reference.get()all'internofunction.applydell'idioma possa esserenull? - C'è qualche possibilità che ci possano essere alcuni problemi come la perdita di memoria?
- Devo fare affidamento su
SoftReferencepiuttosto cheWeakReference?
Grazie.
Risposte
Nota che un metodo come
// multiple instantiation is not a problem;
private static MyAttributeConverter instance() {
if (instance == null) {
instance = new MyAttributeConverter();
}
return instance;
}
non è thread-safe, in quanto sopporta due letture del instancecampo; ognuno di loro può percepire o meno aggiornamenti effettuati da altri thread. Ciò implica che il primo letto in instance == nullpuò percepire un valore più nuovo scritto da un altro thread mentre il secondo in return instance;potrebbe restituire il valore precedente, cioè null. Quindi questo metodo potrebbe restituire nullquando più di un thread lo sta eseguendo contemporaneamente. Questo è un raro caso d'angolo, tuttavia, questo metodo non è sicuro. Avresti bisogno di una variabile locale per assicurarti che il test e l'istruzione return utilizzino lo stesso valore.
// multiple instantiation is not a problem;
private static MyAttributeConverter instance() {
MyAttributeConverter current = instance;
if (current == null) {
instance = current = new MyAttributeConverter();
}
return current;
}
Questo è ancora sicuro solo quando MyAttributeConverterè immutabile utilizzando solo i finalcampi. In caso contrario, un thread potrebbe restituire un'istanza creata da un altro thread in uno stato costruito in modo incompleto.
Puoi usare il modo semplice per renderlo sicuro senza questi vincoli:
private static final MyAttributeConverter instance = new MyAttributeConverter();
private static MyAttributeConverter instance() {
return instance;
}
Questo è ancora pigro poiché l'inizializzazione della classe avviene solo su uno dei trigger specificati , ovvero la prima chiamata del metodo instance().
Il tuo utilizzo di WeakReferenceè soggetto agli stessi problemi. Inoltre, non è chiaro il motivo per cui ricorri a un'invocazione ricorsiva del tuo metodo in due punti in cui hai già l'argomento richiesto in una variabile locale.
Una corretta implementazione può essere molto più semplice:
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);
}
Ma prima di usarlo, dovresti riconsiderare se il codice complicato vale lo sforzo. Il fatto che tu stia accettando la necessità di ricostruire l'oggetto quando è stato raccolto dalla spazzatura, anche potenzialmente costruendo più istanze su invocazioni simultanee, suggerisce che sai che la costruzione sarà economica. Quando la costruzione è economica, probabilmente non è necessario memorizzarne affatto un'istanza.
Considera solo
public static <R> R applyInstance(
Function<? super MyAttributeConverter, ? extends R> function) {
return function.apply(new MyAttributeConverter());
}
Vale almeno la pena provare, misurare le prestazioni dell'applicazione e confrontarle con gli altri approcci.
D'altra parte, non sembra che l'istanza occupasse una quantità significativa di memoria né contenesse risorse non di memoria. Altrimenti, eri più preoccupato per la possibilità che più istanze volassero in giro. Quindi l'altra variante che vale la pena provare e confrontare, è quella mostrata sopra che utilizza un static finalcampo con inizializzazione della classe pigra e nessuna possibilità di garbage collect quel piccolo oggetto.
Un ultimo chiarimento. Hai chiesto
C'è qualche possibilità che
reference.get()all'internofunction.applydell'idioma possa esserenull?
Poiché non vi è alcuna reference.get()invocazione all'interno della valutazione di function.apply, non vi è alcuna possibilità che tale invocazione possa essere valutata nulla questo punto. La funzione riceve un riferimento forte e poiché il codice chiamante ha assicurato che questo riferimento forte non lo sia null, non lo diventerà mai nulldurante l'invocazione del applymetodo.
In genere, il garbage collector non altererà mai lo stato dell'applicazione in modo tale che il codice che utilizza riferimenti forti noterà una differenza (lasciando da parte la disponibilità di più memoria).
Ma poiché hai chiesto specificamente informazioni reference.get(), un garbage collector può raccogliere un oggetto dopo il suo ultimo utilizzo , indipendentemente dalle esecuzioni del metodo o dagli ambiti locali . Quindi il referente potrebbe essere raccolto durante l'esecuzione del applymetodo quando questo metodo non utilizza più l'oggetto. Le ottimizzazioni di runtime possono consentire che ciò avvenga prima di quanto potresti immaginare guardando il codice sorgente , perché quello che potrebbe sembrare un oggetto utilizzato (ad esempio un campo letto) potrebbe non utilizzare l'oggetto in fase di runtime (ad esempio perché quel valore è già contenuto in un Registro della CPU, eliminando la necessità di accedere alla memoria dell'oggetto). Come detto, il tutto senza alterare il comportamento del metodo.
Quindi un ipotetico reference.get()durante l'esecuzione del applymetodo potrebbe in linea di principio valutare null, ma non c'è motivo di preoccuparsi, come detto, il comportamento del applymetodo non cambia. La JVM conserverà la memoria dell'oggetto per tutto il tempo necessario per garantire la corretta esecuzione del metodo.
Ma quella spiegazione era solo per completezza. Come detto, non dovresti usare riferimenti deboli o morbidi per oggetti che non contengono risorse costose.