Rzadkie użycie WeakReference?

Nov 07 2020

Mam klasę, której instancje są inicjowane i używane przez bazową platformę flatform.

class MyAttributeConverter implements AttributeConverter<XX, YY> {

    public YY convertToDatabaseColumn(XX attribute) { return null; }

    public XX convertToEntityAttribute(YY dbData) { return null; }
}

Nic się nie dzieje i pomyślałem, że muszę dodać statyczne metody, które będą używane jako odwołania do metod.

    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);
    }

Nadal nic nie wydaje mi się złe (wierzę) i nie lubię instanceupierać się przy zajęciach i dlatego próbuję to zrobić.

    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); // @@?
    }

Właściwie nie wiem nawet, jak przetestować ten kod. Przepraszam za moje pytania, z których każde może być nieco niejasne.

  • Czy jest to (dobre / złe) podejście?
  • Czy jest jakaś szansa, że reference.get()w function.applyidiomie może znajdować się null?
  • Czy jest szansa, że ​​mogą wystąpić problemy, takie jak wyciek pamięci?
  • Powinienem SoftReferenceraczej polegać niż WeakReference?

Dziękuję Ci.

Odpowiedzi

5 Holger Nov 24 2020 at 20:32

Zwróć uwagę, że metoda taka jak

// multiple instantiation is not a problem;
private static MyAttributeConverter instance() {
    if (instance == null) {
        instance = new MyAttributeConverter();
    }
    return instance;
}

nie jest bezpieczny wątkowo, ponieważ zawiera dwa odczyty instancepola; każdy z nich może odbierać aktualizacje dokonywane przez inne wątki lub nie. Oznacza to, że pierwszy odczyt instance == nullmoże postrzegać nowszą wartość zapisaną przez inny wątek, podczas gdy drugi w return instance;może oceniać do poprzedniej wartości, tj null. Więc ta metoda może powrócić, nullgdy więcej niż jeden wątek wykonuje ją jednocześnie. Jest to rzadki przypadek narożny, jednak ta metoda nie jest bezpieczna. Potrzebujesz zmiennej lokalnej, aby upewnić się, że test i instrukcja return używają tej samej wartości.

// multiple instantiation is not a problem;
private static MyAttributeConverter instance() {
    MyAttributeConverter current = instance;
    if (current == null) {
        instance = current = new MyAttributeConverter();
    }
    return current;
}

Jest to nadal bezpieczne tylko wtedy, gdy MyAttributeConverterjest niezmienne przy użyciu tylko finalpól. W przeciwnym razie wątek może zwrócić wystąpienie utworzone przez inny wątek w stanie niekompletnie skonstruowanym.

Możesz użyć prostego sposobu, aby był bezpieczny bez tych ograniczeń:

private static final MyAttributeConverter instance = new MyAttributeConverter();

private static MyAttributeConverter instance() {
    return instance;
}

Nadal jest to leniwe, ponieważ inicjalizacja klasy ma miejsce tylko na jednym z określonych wyzwalaczy , tj. Przy pierwszym wywołaniu metody instance().


Korzystanie z usługi WeakReferencepodlega tym samym problemom. Co więcej, nie jest jasne, dlaczego uciekasz się do rekurencyjnego wywołania metody w dwóch punktach, w których masz już wymagany argument w zmiennej lokalnej.

Prawidłowa implementacja może być znacznie prostsza:

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);
}

Ale zanim będziesz go używać, powinieneś ponownie rozważyć, czy skomplikowany kod jest wart wysiłku. Fakt, że akceptujesz potrzebę rekonstrukcji obiektu, gdy został on zebrany jako śmieci, nawet potencjalnie konstruując wiele instancji przy jednoczesnych wywołaniach, sugeruje, że wiesz, że konstrukcja będzie tania. Gdy konstrukcja jest tania, prawdopodobnie nie musisz w ogóle buforować jej instancji.

Po prostu zastanów się

public static <R> R applyInstance(
    Function<? super MyAttributeConverter, ? extends R> function) {

    return function.apply(new MyAttributeConverter());
}

Warto przynajmniej spróbować, zmierzyć wydajność aplikacji i porównać ją z innymi podejściami.

Z drugiej strony nie wygląda na to, by instancja zajmowała znaczną ilość pamięci ani nie przechowywała zasobów innych niż pamięć. W przeciwnym razie bardziej martwiłeś się możliwością latania wielu instancji. Tak więc inny wariant, który warto wypróbować i porównać, to ten pokazany powyżej z użyciem static finalpola z leniwą inicjalizacją klasy i bez możliwości wyrzucenia tego małego obiektu do pamięci.


Ostatnie wyjaśnienie. Zapytałeś

Czy jest jakaś szansa, że reference.get()w function.applyidiomie może znajdować się null?

Ponieważ reference.get()w ocenie nie ma inwokacji function.apply, nie ma szans, aby takie wywołanie miało nullw tym momencie wartość. Funkcja otrzymuje silne odwołanie, a ponieważ kod wywołujący zapewniał, że to silne odwołanie nie jest null, nigdy nie stanie się ono nullpodczas wywołania applymetody.

Ogólnie rzecz biorąc, moduł odśmiecania pamięci nigdy nie zmieni stanu aplikacji w taki sposób, że kod używający silnych odwołań zauważy różnicę (pozostawiając na boku dostępność większej ilości pamięci).

Ale ponieważ zapytałeś konkretnie o to reference.get(), odśmiecacz może zebrać obiekt po jego ostatnim użyciu , niezależnie od wykonania metod lub lokalnych zakresów . Zatem referencja mogłaby zostać zebrana podczas wykonywania applymetody, gdy ta metoda nie używa już obiektu. Optymalizacje w czasie wykonywania mogą pozwolić na to wcześniej, niż można się domyślić, patrząc na kod źródłowy , ponieważ coś, co może wyglądać jak użycie obiektu (np. Odczyt pola), może nie używać tego obiektu w czasie wykonywania (np. Rejestr procesora, eliminujący potrzebę dostępu do pamięci obiektu). Jak powiedziano, wszystko to bez zmiany zachowania metody.

Tak więc hipotetyczny reference.get()podczas wykonywania applymetody można w zasadzie ocenić null, ale nie ma powodu do obaw, jak powiedziano, zachowanie applymetody nie zmienia się. JVM zachowa pamięć obiektu tak długo, jak będzie to potrzebne do zapewnienia poprawnego wykonania metody.

Ale to wyjaśnienie miało charakter kompletny. Jak już powiedziano, nie należy używać słabych ani miękkich odniesień do obiektów, które nie posiadają drogich zasobów.