JavaRush /Java блог /Random /Кофе-брейк #106. Почему Set может содержать повторяющиеся...

Кофе-брейк #106. Почему Set может содержать повторяющиеся элементы. Как работает паттерн Singleton

Статья из группы Random

Java: почему Set может содержать повторяющиеся элементы

Источник: DZone В приложениях с малой задержкой избежать создания ненужных объектов можно с помощью повторного использования изменяемых объектов. Это уменьшает нагрузку на память и, следовательно, нагрузку на сборщик мусора. Также это делает приложение более детерминированным и с меньшим джиттером. Однако следует проявлять осторожность в отношении того, как используются эти повторно используемые объекты, иначе могут проявиться неожиданные результаты, например, в форме набора, содержащего повторяющиеся элементы, такие как [B, B]. Кофе-брейк #106. Почему Set может содержать повторяющиеся элементы. Как работает паттерн Singleton - 1

HashCode и Equals

Встроенный в Java ByteBuffer обеспечивает прямой доступ к куче и собственной памяти с использованием 32-разрядной адресации. Chronicle Bytes — это заменяющий компонент с открытым исходным кодом и 64-битной адресацией, позволяющий адресовать гораздо большие сегменты памяти. Оба эти типа предоставляют методы hashCode() и equals(), которые зависят от байтового содержимого базового сегмента памяти объекта. Хотя это может быть полезно во многих ситуациях, такие изменяемые объекты не следует использовать в большинстве встроенных в Java типов Set. Также их не нужно использовать в качестве ключа для большинства встроенных типов Map. Примечание: в действительности только 31 и 63 бит могут использоваться в качестве эффективного смещения адреса (например, с использованием параметров int и long offset).

Изменяемые ключи

Ниже представлен небольшой пример кода, иллюстрирующий проблему с повторно используемыми изменяемыми объектами. Код показывает использование байтов, но та же проблема существует для ByteBuffer.

Set<CharSequence> set = new HashSet<>();
Bytes<?> bytes = Bytes.from("A");
set.add(bytes);

// Reuse
bytes.writePosition(0);

// This mutates the existing object already
// in the Set
bytes.write("B");

// Adds the same Bytes object again but now under
// another hashCode()
set.add(bytes);

System.out.println("set = " + set);
Приведенный выше код сначала добавит объект с “A” в качестве содержимого, что означает, что Set содержит [A]. Затем содержимое этого существующего объекта будет изменено на “B”, что имеет побочный эффект изменения Set, чтобы он содержал [B], но оставит старое значение хэш-кода и соответствующее значение hash bucket без изменений (фактически становится устаревшим). Наконец, измененный объект снова добавляется в Set, но теперь под другим хеш-кодом, ведущим к предыдущей записи для того же самого объекта. В результате вместо ожидаемых [A, B] будет получен следующий результат:

set = [B, B]

Объекты ByteBuffer и Bytes как ключи в Maps

При использовании Java-объектов ByteBuffer или объектов Bytes в качестве ключей Maps или в качестве элементов в Set одним из решений является использование IdentityHashMap или Collections.newSetFromMap(new IdentityHashMap<>()) для защиты от особенностей изменяемых объектов, описанных выше. Это делает хеширование объектов независимым от фактического байтового содержимого и вместо этого будет использовать System.identityHashCode(), который никогда не изменяется в течение жизни объекта. Другая альтернатива — использование версии объектов только для чтения (например, путем вызова ByteBuffer.asReadOnlyBuffer()) и отказ от любых ссылок на исходный изменяемый объект, который мог бы обеспечить лазейку для изменения содержания предположительно доступного только для чтения объекта.

Chronicle Map и Chronicle Queue

Chronicle Map — это библиотека с открытым исходным кодом, которая работает немного иначе, чем встроенные реализации Java Map. Это выражается в том, как объекты сериализуются и помещаются в память вне кучи, открываясь для сверхбольших карт, которые могут быть больше, чем ОЗУ. Память, выделенная JVM, позволяет сохранять эти карты в файлах с отображением памяти, чтобы приложения могли перезапускаться намного быстрее. У процесса сериализации есть еще одно менее известное преимущество, заключающееся в том, что он фактически позволяет повторно использовать изменяемые объекты в качестве ключей, поскольку содержимое объекта копируется и эффективно замораживается каждый раз, когда в Map помещается новая ассоциация. Последующие модификации изменяемого объекта, следовательно, не повлияют на замороженное сериализованное содержимое, позволяя неограниченное повторное использование объекта. Open-source Chronicle Queue работает аналогичным образом и может предоставлять очереди (Queue), которые могут содержать терабайты данных, сохраняемых во вторичном хранилище. По той же причине, что и Chronicle Map, он позволяет повторно использовать изменяемые элементы объектами.

Выводы

В некоторых реализациях Map и Set опасно использовать изменяемые объекты, такие как Bytes и ByteBuffer, где hashCode() зависит от содержимого объекта. IdentityHashMap защищает от повреждения карт и наборов из-за мутации объекта, но делает эти структуры независимыми от фактического байтового содержимого. Доступные только для чтения версии ранее измененных объектов сегментов памяти могут предоставить альтернативное решение. Chronicle Map и Chronicle Queue позволяют неограниченное использование изменяемых объектов, открывая путь к детерминированным операциям с малой задержкой.

Как работает паттерн Singleton

Источник: Dev.to Как известно, паттерн Singleton позволяет создавать только один экземпляр из класса. Давайте рассмотрим случай, когда вашему приложению требуется доступ к общему ресурсу из разных мест в разное время, сохраняя согласованное состояние между каждым доступом. Кофе-брейк #106. Почему Set может содержать повторяющиеся элементы. Как работает паттерн Singleton - 2Паттерн Singleton решает эту проблему, создавая класс, который отвечает за создание только одного экземпляра, разрешая при этом прямой доступ к экземпляру объекта.

Где применяется Singleton

Singleton можно использовать в случаях, когда у класса есть только один доступный экземпляр. Например: Кофе-брейк #106. Почему Set может содержать повторяющиеся элементы. Как работает паттерн Singleton - 3

Участники

Singleton: определяет операцию экземпляра, которая позволяет клиентам получить доступ к его уникальному экземпляру.

Взаимодействие

Клиенты получают доступ к экземпляру Singleton только через метод getInstance.

Преимущества

  • Можно быть уверенным в количестве экземпляров.
  • Можно получить доступ к экземпляру из любого места в коде.

Недостатки

  • Нарушает принцип единой ответственности, поскольку отвечает за создание одного экземпляра и обеспечивает основные функции самого объекта.
  • Модульное тестирование проводить сложнее, потому что трудно изолировать глобальные состояния.
  • Делает объект глобально изменяемым (обеспечивает доступ из любого места в коде), что в некоторых случаях может быть нежелательным.

Выполнение

Синглтоны могут быть реализованы разными способами:
  • Стремительная инициализация: объект класса создается при загрузке в память.
  • Ленивая инициализация: в этом методе объект создается только в случае необходимости.
  • Потокобезопасный синглтон: создается потокобезопасный синглтон, чтобы свойство синглтона сохранялось даже в многопоточной среде.

Демо

Деньги, полученные посетителями кафе, хранятся в кассе. Важно, чтобы в каждом кафе был только один кассовый аппарат, чтобы доход кафе был точно известен. Чтобы решить эту проблему, мы реализуем CashRegister как синглтон. Для этого мы делаем конструктор закрытым, чтобы никто не мог создать экземпляр класса. Также мы реализуем метод getInstance, который будет вызываться пользователем для получения экземпляра класса. Файл main.java:

public class Main {
    public static void main(String[] args) {
        CashRegister register = CashRegister.getInstance();
        register.setMoney(1221.0);
        System.out.println(register.getMoney().toString());
        CashRegister register1 = CashRegister.getInstance();
        System.out.println(register1.getMoney().toString());

    }
}
Файл CashRegister.java:

public final class CashRegister {
    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    private Double money;
    private CashRegister(){}
    private static final CashRegister INSTANCE = new CashRegister();
    public static CashRegister getInstance(){
        return INSTANCE;
    }
}
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
hidden #2924827 Уровень 2
23 декабря 2021
УУУИИИЗЛИИ