Вопрос: Как работает synchronized? — КиберПедия 

Папиллярные узоры пальцев рук - маркер спортивных способностей: дерматоглифические признаки формируются на 3-5 месяце беременности, не изменяются в течение жизни...

Семя – орган полового размножения и расселения растений: наружи у семян имеется плотный покров – кожура...

Вопрос: Как работает synchronized?

2022-10-10 56
Вопрос: Как работает synchronized? 0.00 из 5.00 0 оценок
Заказать работу

Ответ: Ваш поток подкрадывается к этой строчке кода и спрашивает «а мог бы я…?». Его резко обрывают и говорят «нет не мог бы! жди!». У вас дел выше крыши. Вы бы могли там еще поработать, поделать, пошустрить, а вас так «хоп, жди!», следующий подкрадывается «жди!». И что это такое? Это производительный код? Это же бред. А должно быть, как? Вот написан последний пункт. Вы должны спросить: «Я могу обратиться к ресурсу, он свободен?», мне говорят «Занят». Ну я развернулся и ушел. Это же нормально? Да. Так я буду ждать, и у меня будет блокировка в системе, а так я другие дела делаю. Мой поток работает, пашет. Есть запросы от клиента? Мне говорят «Нет, клиент спит». И все? Больше на сервере заняться нечем? Это нормально написать эффективную систему. Должна быть возможность не блокироваться. Кто это должен программировать? Авторы платформы. Это их придумка. Я же не могу влезть и поменять ее.

Еще проблема. Я создал объект Thread. И дальше что? И он уходит в сборку мусора? А мне нужны постоянно потоки, и я создаю Thread. Слышали такое при работе с БД понятие «пул соединения с БД?». Все слышали. Зачем это делается? Это крайне эффективно и производительно, потому что объекты в пуле повторно используются. Вот. Нужен пул потоков. Я не буду создавать объекты Thread, а я их буду получать готовые из пула. А в пуле они будут использоваться многократно. И никакой сборки мусора. И опять? Я могу сделать пул своими руками? Как? Это же надо выходить на native-код. Там же разные ОС. Это в Си можно сделать руками, а не кроссплатформенной Java. И опять это может сделать только автор этой библиотеки.

Еще у нас проблема. Что возвращает run? Ничего. Ну что это за синтаксис? Ну ладно я могу поймать exception, а если она просто завершается? И я еще даже не могу узнать ничего? Мне надо где-то волшебный флажок что ли какой-то выставлять в коде? А потом снова думать о его синхронизации? Это же замкнутый круг получается. Нет, не надо. Дайте мне нормальный тип возвращаемого значения, чтобы я мог посмотреть.

И вот еще надо бы парочку хороших паттернов, чтобы вы запрограммировали. Потому что этот вот вариант, который мы можем сделать с помощью synchronize (вспоминаем ОМОН с рынком) производительность была никакая. А как сделано в СУБД? Ведь у нас перед глазами хорошая и эффективная реализация. Там профессионалы годами бьются. Все уже придумано давно. Если таблицу читают сотни клиентов, они же ее не блокируют. Хотя есть блокировка на чтение. Но она же не дает другим читающим? Нет.

Голос из толпы: Есть системы где блокируют.

Ответ: Есть разные варианты, но если ты читаешь и у тебя блокировка на чтение, то другим же разрешено читать?

Голос из толпы: Есть системы где нет.

Ответ: Вот у нас была такая система с synchronized. Вы получили synchronized-доступ? Все заблокировано. Не важно, что делает человек. вызывает он гетер или сетер. А ведь как можно было бы эффективно сделать? Сотни читают вместе и одновременно. А когда надо кому-то менять, эту сотню пропустил, новым читающим дал отбой – пусть ждут. И потом раз, кто-то изменил данные – и снова потом читаем.

Поэтому смотрите. Идеи уже существуют. Они уже отлажены и апробированы на уровне этих реляционных баз данных. А мы тут сидим со словом synchronized. 19 век.

Поэтому, наконец-то, в версии 1.5 они эти проблемы попытались решить.

Рассмотрим пример DEMO4.

Кстати, учтите они попытались решить все проблемы в одной версии. Конечно, потом они улучшают, добавляют, дописывают. Но вот прям такой прорыв был в версии 1.5. Кстати, если кто-то будет искать специальную продвинутую литературу по Java, есть отдельные книжки посвященные потокам. И старайтесь искать не по 1.5, а по 1.7 хотя бы. К сожалению, не все там книжки читабельны. Мне в руки попалась одна очень страшная книжка, где автор только код практически распечатывал. Комментариев вообще по минимуму. Итак, пример мы открыли и давайте смотреть.

 

Начнем с Main. В Mainвызывается одна вспомогательная функция Solve, которая все и раскручивает. С нее мы и начнем.

Если пропустить создание коллекций, то строка, которая идет ниже смотрите как выглядит:

Executors.newFixedThreadPool(5) – это пул потоков. И классов, которые позволяют работать вам с пулами потоков он не один. Их четыре-пять точно разных вариантов. Мы посмотрим на них и увидим на слайде. Ну, например, этот чем отличается от всех остальных? Здесь фиксированное число потоков в пуле. Написаливскобочкахпараметр, значитихбудет 5. Ивсё. Это значит не получится по какой-то причине, что вы скушаете все ресурсы на компьютере, правильно? 5 и больше никаких потоков в этот пул не позволит создать. Хотя при этом вы можете задач поставить перед этим пулом сколько угодно, но именно пять потоков будет выполнять эту работу. Причем это будут одни и те же потоки (повторное использование). Значит эффективность здесь уже намного выше, чем при обычном создании объектов класса Thread. Пул потоков назвали здесь «es». Как в этот пул поставить задачи? Посмотрите. es.submit и дальше идет объект какого-то класса MyCallable. Но если глянуть на этот sumbit, то тут можно как-то перегрузку посмотреть? Да.

· <T> Future<T> submit(Callable<T> task);

· <T> Future<T> submit(Runnable task, T result);

· Future<?> submit(Runnable task);

Вы видите Runnable? Это значит, что можно использовать весь тот код, который у вас был до этого накоплен. Вы писали классы, реализовывали интерфейсы Runnable – ради бога. Эти новые классы все это понимают.

Но т.к. хочется что-то поинтереснее, вот у нас еще появился один интерфейс под названием Callable. Смотримнанего. Итак. ИщемэтотMyCallable. Потому что он ниже, и как раз реализует интерфейс Callable. Конечно, называлиметоднеrun, аcall. Ачтоонвозвращает? ОнвозвращаетGeneric. ПоступиливстилеJava. Вот вы говорили, что хотелось бы простой тип, чтобы не создавать эти объекты. Нет, ребята Javaпрограммисты, они так не могут. Они объектами любят манипулировать. Поэтому у них Generic-интерфейс и вы выбираете. В данном случае мы выбрали, например, когда здесь вы видели MyCallable<String>, то туда как параметр идет Generic. Простые типы, к сожалению с Genericне работают. Но это такая здесь проблема, которая существует давно и долго. Ну вот, хотя бы тип возвращаемого значения появился. А теперь посмотрите еще одно слово, которое идет после call. throwsException! Они сам этот интерфейс с Callableзаложили, что он может выбрасывать любые исключения. Поэтому, если вы не хотите обрабатывать, то вас никто не будет заставлять, как мы раньше обрабатывали InterruptedException. Вы можете любые исключения выбрасывать, потому что здесь “throwsException”. Завершаясь, метод call возвращает вам результат. И теперь возникает вопрос, а где я его получу? Я накидал 700 задач. Вот у меня цикл. Япоставлюздесьне 7, а 700. Я 700 задачпоставлю. Икакяэтотрезультатполучу? Где? Когда? Смотрите… Этот результат, который возвращает метод call, ведь не вы же его вызываете, а точка входа. Его submitвернет этот результат. С другой стороны, как оно может вернуть, если вызов еще даже не произошел? Япоставлю 700 задач. Я submitвызываю прямо здесь. Они еще работать не начали, у меня ответов нет, как он вернет что-нибудь? И им пришлось крепко подумать и выкручиваться из этой ситуации. Как вернуть то, чего еще нет? nullже нельзя вернуть. И они поступили следующим образом.submitна самом деле возвращает не совсем ваш тип данных, который вы в данном случае как stringуказали. submitвозвращает объект класса Future. Этопростокласс. Егообъектможносоздать? Да. Вот внутри этого объекта Futureи появится ваш результат, когда метод Callзакончит работать. У вас будет коллекция (ArrayList) ответов будущих.

Ответ:
run: done done done done pool-1-thread-1 pool-1-thread-2 pool-1-thread-3 done pool-1-thread-4 pool-1-thread-5 done done pool-1-thread-5 pool-1-thread-3 СБОРКА УСПЕШНО ЗАВЕРШЕНА (общее время: 2 секунды)

Вы спросите «ну ведь там же получается, что внутри каждого объекта Futureрезультата еще нет. Ну нет, а коллекция-то есть. Вы же можете по ней ходить и спросить, сколько у вас там элементов. Главноетольконеобращатьсякэтомуполю, которогонет. Почему? А вот посмотрите, внизу написан цикл. Мы как раз запустили работу, а потом говорим: «Хочу результат посмотреть!». Я беру каждый элемент из коллекции, вызываю get. getкак раз и возвращает именно ваши данные. Так вот запомните, что getэто блокирующий метод, т.е. если результата еще нет, то вы в этом getбудете блокированы до тех пор, пока он не появится. Выскажете: «Опятькапкан?». Нучтовы, ейбогу! Какойкапкан! Простокодможнобылописатьпо-разному. Вот посмотрите, здесь есть нормальный метод isDone.

 

Понятно, да? Ну не вызывайте сразу get(). Спросите «есть результат?», вам скажут, что его нет, идите к следующему элементу. Т.е. тут сделано уже все по уму. Ребята учатся писать код. Поэтому пожалуйста, можете всем здесь управлять.

Здесь только обратите внимание, что пул – это ресурс, который вы должны освобождать. Поэтому у объекта ExecutorServiceесть много разных вызовов. Там есть добрые варианты, жесткие варианты. Можно по-разному завершать ресурс. Можно дать всем досчитаться. Например, shutdown, который здесь вызывается – он добрый. Он говорит: «пусть все доработают». Наверно, он не даст возможности ставить новые задачи на выполнение в этот пул, но те кто там уже оказался – им дадут доработать.

А если вы закомментируете это (es.shutdown();), и раскоментируете строчку ниже (es.shutdownNow();) – это вариант жесткий, т.к. я говорю «Закрыть сейчас же!».

Вывод программы:

Видите? Даже исключение вылетело! Он говорит: «Как так можно работать? Нас всех поубивали!». Ну да. Давайте я поставлю Thread.sleep(2000) в главный поток, потому что если там не поставить ничего, то их прямо на середине работы останавливают. Они начали работать и «бах» всех остановили. Вот видите? Первая пятерка отработала, а когда вторая двойка пошла (там же у меня 7 работало), то их прям сразу отсекли. Но это жесткий вариант. Но это уже на ваш выбор.

Вывод программы:

Создание пула потоков

Мы смотрели и видели, что у нас есть пул фиксированного размера. Но это же не на все случаи жизни? Нет, конечно. Вот здесь несколько показано других вариантов. Смотрите, что у нас есть.

Есть newCachedThreadPool – это безразмерный пул. Ну мало ли вы не знаете, сколько вам нужно.

newFixedThreadPool(5) – фиксированный мы видели.

Кстати, в классе Executersесть один замечательный метод, который позволяет создать пул ровно из одного потока. Вот нужен вам один поток? А потом может быть будет нужен еще один поток. Ну пользователь в интерфейсе задает какие-то кнопочки, кликает. Ну что он, 10 раз нажмет и вычислит статистику? Вы же должны запретить это. Нажал вычислить статистику и эту кнопку блокируете. Достаточно одного потока вспомогательного, но не факт. Но если достаточно, вот там берете просто один поток и все – пул из одного потока.

newScheduledThreadPool(2) – это вариант с бизнес-логикой. Допустим вы ставите 100 задач, одновременно. Но 100 задач одновременно на 5 потоках??? Все-равно только две задачи смогут стартовать, правильно? Помните были ОС Windows, которые медленно стартовали. По-моему, Vistaочень долго загружалась. Что они сделали в следующей версии? Они сделали так: вначале запускаются все сервисы и службы без которых невозможно работать операционной системе, а остальные даже не пытаются стартовать, т.к. они запускаются со сдвигом. Просто пауза, они ждут, не мешают, не создают толкучку. А потом нормально, через 25 секунд начинают подниматься. Тут тоже самое. Вы ставите задачу и говорите: «А ты через 2 секунды!». И вы можете со сдвигом поставить задачу и соответственно не создать такое массовое столпотворение, когда ресурсов точно не хватит и будут проблемы. Это вполне может пригодиться.

P.S.: Since Java 5, the Java concurrency API provides a mechanism that aims at resolving problems. This mechanism is called the Executor framework and is around the Executor interface, its subinterface ExecutorService, and the ThreadPoolExecutor class that implements both interfaces. This mechanism separates the task creation and its execution. With an executor, you only have to implement the Runnable objects and send them to the executor. It is responsible for their execution, instantiation, and running with necessary threads. But it goes beyond that and improves performance using a pool of threads. When you send a task to the executor, it tries to use a pooled thread for the execution of this task, to avoid continuous spawning of threads. Another important advantage of the Executor framework is the Callable interface. It's similar to the Runnable interface, but offers two improvements, which are as follows: - The main method of this interface, named call(), may return a result. - When you send a Callable object to an executor, you get an object that implements the Future interface. You can use this object to control the status and the result of the Callable object.

 


Поделиться с друзьями:

Опора деревянной одностоечной и способы укрепление угловых опор: Опоры ВЛ - конструкции, предназначен­ные для поддерживания проводов на необходимой высоте над землей, водой...

Индивидуальные и групповые автопоилки: для животных. Схемы и конструкции...

Кормораздатчик мобильный электрифицированный: схема и процесс работы устройства...

Адаптации растений и животных к жизни в горах: Большое значение для жизни организмов в горах имеют степень расчленения, крутизна и экспозиционные различия склонов...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.024 с.