Может ли синхронизация внутреннего блока улучшить производительность уже синхронизированного метода?
Дана теоретическая система, в которой файлы 9X_jre загружаются из Интернета, если они не найдены 9X_java в локальной системе, и предполагается:
- Механизм загрузки и извлечения/размещения из/в кэше (локальный файловая система) уже позаботились.
- Однопоточный и одиночный запрос на URL.
Я 9X_multithreading написал метод, использующий getFileFromLocalFS() и 9X_openjdk getFileFromWeb() для реализации упрощенной 9X_concurrent-programming логики кеширования:
public InputStream getFile(String url) { // #1
InputStream retStream = getFileFromLocalFS(url);
if (retStream != null) {
return retStream;
}
else {
retStream = getFileFromLocalFS(url);
if (retStream == null) {
return getFileFromWeb(url);
}
}
return retStream;
}
Затем это схематическое 9X_concurrency решение необходимо было улучшить, чтобы 9X_jdk оно соответствовало одновременным запросам на загрузку 9X_jre с одного и того же URL-адреса... и ограничивало 9X_concurrency фактическую "из Интернета" одиночной загрузкой 9X_multithread (т. е. все другие запросы будут получать 9X_threads ее из локальной файловой системы) . Итак, я 9X_javax синхронизировал весь метод:
public synchronized InputStream getFile(String url) { // #2
InputStream retStream = getFileFromLocalFS(url);
if (retStream != null) {
return retStream;
}
else {
retStream = getFileFromLocalFS(url);
if (retStream == null) {
return getFileFromWeb(url);
}
}
return retStream;
}
Это, по сути, соответствует 9X_multithread запросу, но имеет проблему с производительностью, поскольку 9X_synchronized предотвращает запуск всего метода другим 9X_threads потоком до его завершения. То есть, даже 9X_concurency если файл может быть извлечен из локальной 9X_threading ФС, доступ к getFileFromLocalFS(url)
невозможен, пока метод выполняется 9X_thread другим потоком.
Улучшение производительности, предложенное 9X_thread моим интервьюером, заключалось в синхронизации 9X_jre блока getFileFromLocalFS(url)
:
public synchronized InputStream getFile(String url) { // #3
InputStream retStream = getFileFromLocalFS(url);
if (retStream != null) {
return retStream;
}
else {
synchronized (this) {
retStream = getFileFromLocalFS(url);
if (retStream == null) {
return getFileFromWeb(url);
}
}
}
return retStream;
}
Я сказал "хорошо, но чтобы эта 9X_threads оптимизация работала, нужно убрать синхронизацию 9X_concurency методов", т.е.:
public InputStream getFile(String url) { // #4
InputStream retStream = getFileFromLocalFS(url);
if (retStream != null) {
return retStream;
}
else {
synchronized (this) {
retStream = getFileFromLocalFS(url);
if (retStream == null) {
return getFileFromWeb(url);
}
}
}
return retStream;
}
Интервьюер не согласился 9X_concurrency и настоял на том, чтобы оставить обоих synchronized
на месте.
Какой 9X_concurrent-programming из них лучше работает в среде параллелизма? №3 9X_threading или №4? Почему?
Ответ #1
Ответ на вопрос: Может ли синхронизация внутреннего блока улучшить производительность уже синхронизированного метода?
Кажется, происходит Cargo Cult Programming. Третий вариант напоминает 9X_concurrent-programming Double-Checked Locking, но не улавливает сути. Но что еще более 9X_multithreading поразительно, первый вариант также напоминает Double-Checked 9X_concurrent-programming Locking, вызывая getFileFromLocalFS(url)
дважды без причины. И 9X_synchronized это отправная точка…
Ваше подозрение относительно 9X_concurrent «улучшения» интервьюера верно. Вложенная 9X_synchronized синхронизация бесполезна, и в лучшем случае 9X_java оптимизатор JVM сможет ее устранить.
Однако 9X_concurency ни одно из решений не рекомендуется. Тут 9X_thread гораздо более глубокая проблема. Во-первых, мы 9X_concurrency должны сосредоточиться на правильности, прежде чем обсуждать 9X_javax производительность.
Судя по всему, getFileFromLocalFS(url)
должен 9X_concurency найти локальную копию, если она существует, а 9X_javax не создать ее. Это было бы бессмысленно, если 9X_concurrent бы никто никогда не создавал такие копии, и 9X_concurrency весь вопрос о перекрывающихся попытках кэширования 9X_concurrent-programming был бы спорным, если этот метод не выполняет 9X_concurency попытку кэширования. Таким образом, единственным 9X_threads другим задействованным методом, getFileFromWeb(url)
, должен 9X_multithread быть тот, который создает локальную копию.
Это 9X_jdk означает, что между этими двумя методами 9X_java существует скрытый протокол. getFileFromLocalFS(url)
каким-то образом знает, как 9X_jdk получить кешированный файл, созданный getFileFromWeb(url)
.
Это 9X_concurency вызывает вопросы, например, что произойдет, если 9X_jre один поток находится в середине getFileFromWeb(url)
создания 9X_synchronized кэшированной версии, а другой поток вызывает 9X_java getFileFromLocalFS(url)
для того же URL-адреса? Возвращает ли он 9X_concurency входной поток в еще незавершенный локальный 9X_synchronized файл? Есть две возможности:
Этот вопрос как-то 9X_thread решился. Это будет означать, что за кулисами 9X_concurency уже существует некоторая поточно-безопасная 9X_java конструкция для предотвращения такого состояния 9X_concurrent гонки, следовательно, она должна использоваться 9X_jre
getFileFromWeb(url)
для решения проблемы множественных попыток 9X_concurrent-programming кэширования в первую очередь.Или эта проблема 9X_concurrent не была решена и отправной точкой был сломанный метод, поэтому 9X_jdk в первую очередь нужно исправить это, а 9X_concurrency не пытаться улучшить производительность. Если 9X_threading мы действительно попытаемся исправить это 9X_thread вне двух методов, т.е. в
getFile
, правильным будет 9X_concurrency только второй вариант, вызывающий оба метода 9X_javax внутриsynchronized
.
В любом случае метод getFile
не подходит 9X_concurrent-programming для решения проблемы. Проблемы параллелизма 9X_synchronized должны решаться с помощью скрытого протокола, который уже 9X_concurrent существует между двумя методами.
Например:
Если 9X_concurrent эти два метода используют сопоставление 9X_jdk URL-адреса с локальным файлом, они должны 9X_jdk использовать concurrent map и его атомарные операции обновления 9X_javax для решения проблем параллелизма.
Если эти 9X_thread два метода используют схему сопоставления 9X_multithreading для преобразования URL-адреса в локальный 9X_threading путь, где должна быть копия, а метод
getFileFromLocalFS(url)
просто 9X_multithread проверяет наличие файла, методgetFileFromWeb(url)
должен создать 9X_multithreading файл с опциейCREATE_NEW
для атомарной проверки наличия 9X_concurrency и создания, если нет, вместе с блокировкой 9X_openjdk файлов для предотвращения чтения другими 9X_jre потоками, пока кэширование все еще выполняется.
Ответ #2
Ответ на вопрос: Может ли синхронизация внутреннего блока улучшить производительность уже синхронизированного метода?
Оба они в основном (несколько затененные) версии 9X_threading double-checked locking pattern. Но, учитывая, что вызов getFileFromLocalFS(url)
сам по себе может 9X_jre быть дорогостоящим (это вызов ввода-вывода, который 9X_jre всегда более затратен, чем операции в памяти), эта 9X_jdk форма шаблона на самом деле не идеальна. В 9X_thread идеале реализация блокировки с двойной проверкой 9X_openjdk должна быть максимально "плотной", то 9X_thread есть две блокировки должны быть как можно 9X_openjdk ближе друг к другу.
Дополнительная синхронизация 9X_threads в #3 (предложение интервьюера) создает дополнительные 9X_synchronized накладные расходы и, что более важно, потенциально 9X_threads блокирует, когда в этом нет необходимости. "Производительность" — это 9X_javax широкий термин, который может означать разные 9X_concurrent-programming вещи для разных людей и в разных контекстах; но 9X_concurrent-programming эта дополнительная синхронизация может (вероятно?) повлиять 9X_synchronized на наблюдаемую пропускную способность в зависимости от обстоятельств.
-
22
-
12
-
10
-
16
-
4
-
16
-
4
-
10
-
4
-
4
-
8
-
4
-
4
-
3
-
6
-
6
-
10
-
10
-
5
-
3
-
3
-
4
-
6
-
3
-
4
-
3
-
1
-
1
-
2
-
3
-
1
-
2
-
1
-
2
-
2
-
4
-
5
-
10
-
5
-
8
-
25
-
9
-
8
-
8
-
21
-
5
-
3
-
12
-
9
-
9