Ответ 1
Я пытаюсь найти примеры реальной жизни, которые я видел в дикой природе (возможно, слегка изменился, но основные проблемы были очень реальными). Я также попытался сгруппировать их по одному и тому же сценарию, так что вы можете легко создать сеанс.
Сценарий: у вас есть трудоемкая функция, которую вы хотите делать много раз для разных значений, но те же самые значения могут появляться снова (в идеале, не слишком долго после ее создания). Хорошим и простым примером являются пары URL-адресов веб-страниц, которые необходимо загрузить и обработать (для упражнений это должно быть, вероятно, смоделировано).
Циклы:
-
Вы хотите проверить, появляется ли какой-либо набор слов на страницах. Используйте свою функцию в цикле, но с тем же значением, псевдо-код:
for (word : words) { checkWord(download(url)) }
Одно решение довольно просто, просто загрузите страницу перед циклом. Другое решение ниже.
Утечка памяти:
- простой: вы также можете решить свою проблему с помощью своего кеша. В простейшем случае вы можете просто поставить результаты на (статическую) карту. Но если вы не помешаете этому, его размер будет расти бесконечно → утечка памяти.
Возможное решение: используйте карту LRU. Скорее всего, производительность не будет сильно ухудшаться, но утечка памяти должна исчезнуть. - сложнее: скажем, вы используете предыдущий кеш с помощью
WeakHashMap
, где ключи являются URL-адресами (НЕ как строки, см. ниже), значения - это экземпляры класса, содержащего URL-адрес, загруженную страницу и что-то еще, Вы можете предположить, что это должно быть хорошо, но на самом деле это не так: поскольку значение (которое слабо ссылается) имеет ссылку на ключ (URL), ключ никогда не сможет быть очищен → хорошая утечка памяти.
Решение: удалите URL-адрес из значения. - То же, что и раньше, но URL-адреса являются интернированными строками ( "для сохранения некоторой памяти, если мы снова будем иметь одни и те же строки" ), значение не относится к этому. Я не пробовал, но мне кажется, что это также вызовет утечку, потому что интернированные строки не могут быть GC-ed.
Решение: не стажер, который также приведет к советам, которые вы не должны пропускать: не делайте преждевременную оптимизацию, так как это корень всех злых.
Создание объекта и строки:
- Предположим, вы хотите отображать только текст страниц (~ удалить html-теги). Напишите функцию, которая делает это по очереди, и добавляет ее к растущему результату. Сначала результат должен быть строкой, поэтому добавление займет много времени и распределение объектов. Вы можете обнаружить эту проблему с точки зрения производительности (почему приложения настолько медленны) и с точки зрения создания объектов (почему мы создали так много строк, StringBuffers, массивов и т.д.).
Решение: для получения результата используйте StringBuilder.
Concurrency:
-
Вы хотите ускорить загрузку всего файла путем параллельной загрузки/фильтрации. Создайте некоторые потоки и запустите свой код, используя их, но делайте все внутри большого синхронизированного блока (на основе кеша), просто "чтобы защитить кеш от проблем concurrency". Эффект должен состоять в том, что вы эффективно используете только один поток, так как все остальные ждут, чтобы получить блокировку в кеше.
Решение: синхронизировать только вокруг операций кеша (например, использовать `java.util.collections.synchronizedMap()) -
Синхронизируйте все крошечные кусочки кода. Это должно убить производительность, возможно, предотвратить нормальное параллельное выполнение. Если вам повезет/умнее, вы также можете придумать мертвый замок. Мораль: синхронизация не должна быть специальной, на основе "не повредит", но хорошо продуманная вещь.
Бонусное упражнение:
Заполните свой кеш в начале и не делайте слишком много выделения после этого, но все равно будете иметь небольшую утечку. Обычно этот шаблон не так легко поймать. Вы можете использовать функцию "закладки" или "водяного знака" профилировщика, которая должна быть создана сразу после завершения кэширования.