java concurrency на практике гетц брайан

Java concurrency на практике гетц брайан

Автор: Гетц Брайан, Пайерлс Тим, Блох Джошуа, Боубер Джозеф, Холмс Дэвид, Ли Даг
Год: 2020
Издательство: Питер
ISBN 978-5-4461-1314-9
Страниц: 464
Язык: Русский
Формат: PDF
Размер: 7 Mb

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

Улучшенная виртуальная машина Java, поддержка высокопроизводительных классов и богатый набор строительных блоков для задач распараллеливания стали в свое время прорывом в разработке параллельных приложений.

В «Java Concurrency на практике» сами создатели прорывной технологии объясняют не только принципы работы, но и рассказывают о паттернах проектирования. Легко создать конкурентную программу, которая вроде бы будет работать. Однако разработка, тестирование и отладка многопоточных программ доставляют много проблем.

Код перестает работать именно тогда, как это важнее всего: при большой нагрузке. В «Java Concurrency на практике» вы найдете как теорию, так и конкретные методы создания надежных, масштабируемых и поддерживаемых параллельных приложений.

Авторы не предлагают перечень API и механизмов параллелизма, они знакомят с правилами проектирования, паттернами и моделями, которые не зависят от версии Java и на протяжении многих лет остаются актуальными и эффективными.

Источник

Java Concurrency на практике, Гетц Брайан, Пайерлс Тим, Блох Джошуа, Боубер Джозеф, Холмс Дэвид, Ли Даг, 2020

К сожалению, на данный момент у нас невозможно бесплатно скачать полный вариант книги.

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

Также можно купить бумажную версию книги здесь.

Java Concurrency на практике, Гетц Брайан, Пайерлс Тим, Блох Джошуа, Боубер Джозеф, Холмс Дэвид, Ли Даг, 2020.

Потоки есть везде.
Даже если ваша программа не создает поток явно, структуры могут создавать потоки от вашего имени, и код, вызываемый из этих потоков, должен быть потокобезопасным. Это добавляет разработчикам проблем.

JVM-машина при запуске создает потоки для служебных задач (например, сбора мусора) и главный моток для работы метода main. Инструментальный пакет AWT (Abstract Window Toolkit) и платформа программирования пользовательского интерфейса Swing создают потоки для управления событиями пользовательского интерфейса. Класс Timer создает потоки для выполнения отложенных задач. Компонентные структуры создают пулы (или пучки) потоков и активируют в них компонентные методы.

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

ОГЛАВЛЕНИЕ.
Отзывы.
Листинги.
Предисловие.
Как пользоваться книгой.
Примеры исходного кода.
Благодарности.
От издательства.
Глава 1. Введение.
ЧАСТЬ I. ОСНОВЫ.
Глава 2. Потокобезопасность.
Глава 3. Совместное использование объектов.
Глава 4. Компоновка объектов.
Глава 5. Строительные блоки.
ЧАСТЬ II. СТРУКТУРИРОВАНИЕ КОНКУРЕНТНЫХ ПРИЛОЖЕНИЙ.
Глава 6. Выполнение задач.
Глава 7. Отмена и выключение.
Глава 8. Применение пулов потоков.
Глава 9. Приложения с GUI.
ЧАСТЬ III. ЖИЗНЕСПОСОБНОСТЬ, ПРОИЗВОДИТЕЛЬНОСТЬ И ТЕСТИРОВАНИЕ.
Глава 10. Предотвращение сбоев жизнеспособности.
Глава 11. Производительность и масштабирование.
Глава 12. Тестирование конкурентных программ.
ЧАСТЬ IV. ПРОДВИНУТЫЕ ТЕМЫ.
Глава 13. Явные замки.
Глава 14. Построение настраиваемых синхронизаторов.
Глава 15. Атомарные переменные и неблокирующая синхронизация.
Глава 16. Модель памяти Java.
ПРИЛОЖЕНИЕ А. АННОТАЦИИ ДЛЯ КОНКУРЕНТНОСТИ.
A.1. Аннотации классов.
A.2. Аннотации полей и методов.
Библиография.

По кнопкам выше и ниже «Купить бумажную книгу» и по ссылке «Купить» можно купить эту книгу с доставкой по всей России и похожие книги по самой лучшей цене в бумажном виде на сайтах официальных интернет магазинов Лабиринт, Озон, Буквоед, Читай-город, Литрес, My-shop, Book24, Books.ru.

По кнопке «Найти похожие материалы на других сайтах» можно найти похожие материалы на других сайтах.

On the buttons above and below you can buy the book in official online stores Labirint, Ozon and others. Also you can search related and similar materials on other sites.

Источник

Актуальна ли книга «Java Concurrency in Practice» во времена Java 8 и 11?

Статья, перевод которой мы предлагаем сегодня, в очередной раз напоминает о важности нестареющей книги «Java Concurrency in practice» под авторством Брайана Гёца (Brian Goetz).

Даты комментариев к этой статье в оригинале подсказывают, что автор обновляет и заново публикует ее не в первый раз. Поэтому мы позволили себе также обновить ссылку на упоминаемую в статье книгу Рауля-Габриэля Урма, Марио Фуско и Алана Майкрофта, которая выходила в издательстве «Manning» под названием «Java 8 in Action». У нас готовится перевод нового издания под названием «Modern Java». Но пока давайте поговорим о классике. Вы приглашаетесь под кат.

Читатель под ником Shobhit задал мне этот вопрос в комментариях к статье о 12 продвинутых книг по Java для программистов среднего уровня – часть 1. Вопрос в самом деле хороший, и, думаю, у многих Java-программистов возникали подобные сомнения, когда в наше время кто-нибудь рекомендовал им прочесть «Java Concurrency in Practice». Когда эта книга только вышла, в 2006 году, весь мир Java еще никак не мог разобраться с нововведениями в области конкурентности, сделанными в Java 1.5. Думаю, тогда была предпринята первая серьезная попытка улучшить в Java встроенную поддержку многопоточности и конкурентности. Тогда многие программисты еще даже не подозревали о новых инструментах, появившихся в API, например о CountDownLatch, CyclicBarrier, ConcurrentHashMap и многих других. Книга служила им отлаженным введением в работу с этими инструментами, рассказывала, как с их помощью можно писать высокопроизводительные конкурентные приложения Java.

Именно таково общее представление об этой книге, и в таком ключе опишут ее вам многие разработчики, если вы спросите «А как вам «Java Concurrency in Practice»»? Однако, я воспринимаю эту книгу немного иначе, и именно поэтому до сих пор рекомендую ее всем новичкам, знакомящимся с Java, либо разработчикам среднего уровня, желающим освоить концепции, связанные с конкурентностью.

Важнейший материал, с которым вас познакомит эта книга – четкие концепции и основы конкурентного программирования, в частности, видимость, упорядочивание, потокобезопасность, неизменяемость, параллелизм, т.д.

Также она объясняет, почему в большинстве случаев конкурентные приложения на Java пишутся неправильно, почему Java-программисты допускают распространенные ошибки, приводящие к проблемам с многопоточностью, среди которых: условия гонки, мертвая блокировка, активная блокировка, интерференция в памяти и просто некорректные вычисления.

В книге используются эмотиконы, сопровождающие описание «как делать неправильно», прежде, чем познакомить читателя с верным и качественным решением проблемы. Так книга не только помогает бороться с заблуждениями, которые сохраняются у многих Java-разработчиков, но и прививать в Java-сообществе верную информацию о том, как работать с многопоточностью и конкурентностью.

Вне всяких сомнений, многопоточность и конкурентность сложны. Их нелегко правильно реализовать в коде, не менее трудно понять и объяснить. Я знаю много программистов, попросту не способных наглядно изобразить, как разные потоки взаимодействуют с одним и тем же элементом кода, оперируя разными данными.

Читайте также:  успенский желтиков мужской монастырь

Как и классический пример с рекурсией, одним программистам многопоточность очень легко дается на практике, а другим многопоточность сложно осмыслить и применить в прикладном сценарии.

Наибольший вклад книги Java Concurrency in Practice в развитие мира Java заключается не в том, чтобы упростить конкурентность, а в том, чтобы дать о ней верную и точную информацию, которой не хватало. С тех пор я провел множество собеседований и знаю, что программисты всегда не вполне точно представляют себе устройство потоков и то, как они работают.

Многие программисты, даже успевшие поработать с Java 4-5 лет, не понимают, как устроены изменчивые переменные; все, что они знают – что при работе с изменчивой переменной нужно при каждом сравнении проверять, какое значение находится в основной памяти. Это правда, но только часть правды.

Им не рассказывали о модели памяти Java, о том, как изменчивая переменная может влиять на порядок следования кода и выполнение вычислительных инструкций в его основе. Речь о применении динамической компиляции (JIT) и виртуальной машины Java (JVM) для оптимизации; такая оптимизация может приводить к тонким логическим ошибкам.

Людям не рассказывают, как изменчивые переменные позволяют увидеть, что было сделано в одном потоке, прежде чем обращаться к переменной из другого потока и пр. Не все знают, что такое барьер памяти, и как он влияет на видимость.

Именно по книге Java Concurrency in Practice многие Java-программисты изучили все эти концепции. Должен признать, что и сам я, пока не прочитал ее, во многом заблуждался по поводу многих существенных вопросов многопоточности и конкурентности, в частности, порядка следования, видимости и неявных эффектов со стороны финальных переменных и безопасной публикации. Книга помогла мне во всем этом разобраться.

Кстати, если какие-то разделы книги покажутся вам немного непонятными – поверьте, не вам одному. Здесь следует поблагодарить доктора Хайнца Кабуца (Heinz Kabutz), изложившего материал книги в упрощенном виде в своем курсе Java Concurrency in Practice Bundle.

Если даже этот материал покажется вам слишком сложным – у Хайнца есть и другой курс, Mastering Threads, помогающий разобраться в многопоточности любому среднестатистическому Java-программисту.

Также у нас появились потоки (stream) и параллельные потоки (parallel streams), позволяющие разработчикам пользоваться преимуществами конкурентности, не программируя ее. Вся идея о том, чтобы забрать реализацию конкурентности у разработчиков приложений и перепоручить ее разработчикам API немного упрощает ситуацию с конкурентностью в Java и снижает риски при ее внедрении.

Также это означает, что в Java можно выполнять массовые операции во множестве потоков при помощи всего пары методов, и не писать при этом ни единой строки кода, связанной с потоками, ключевым словом synchronized или методами ожидания и уведомления (wait-notify).

Несомненно, любому Java-разработчику требуется изучить эти новые инструменты, чтобы не отставать развития технологии – в чем, конечно, очень поможет книга вроде «Modern Java In Action». Она не только познакомит вас со всеми нововведениями языка Java, но и поможет научиться использовать их при выполнении повседневных задач, понять мотивацию этих нововведений и получить общее представление о современном языке Java.

Хотя, книга Java Concurrency in Practice в ее текущем виде не освещает всех этих важных концепций и инструментов, она все равно остается бесценной для изучения ключевых возможностей языка Java, связанных с потоками, конкурентностью и многопоточностью.

Книга Гёца по-прежнему обязательна к прочтению для любого Java-разработчика, желающего изучить и освоить многопоточность и конкурентность – самые сильные стороны Java при разработке приложений.

С учетом всего вышесказанного я, как и многие Java-разработчики по всему миру, хотел бы увидеть и обновленное издание Java Concurrency in Practice, где рассматривались бы инструменты и методологии, появившиеся в Java 6, 7, 8, 9, 10 и может быть даже в Java 11. Ведь появились обновленные версии Effective Java и Head First design patterns, рассматривающие Java 8 и демонстрирующие, насколько проще реализовывать различные паттерны при помощи новых возможностей Java 8.

Источник

Книга «Java Concurrency на практике»

Отрывок. Потокобезопасность

Возможно, вас удивит, что конкурентное программирование связано с потоками или замками (1) не более, чем гражданское строительство связано с заклепками и двутавровыми балками. Разумеется, строительство мостов требует правильного использования большого количества заклепок и двутавровых балок, и то же самое касается построения конкурентных программ, которое требует правильного использования потоков и замкˆов. Но это всего лишь механизмы — средства достижения цели. Написание потокобезопасного кода — это, по сути, управление доступом к состоянию и, в частности, к совместному (shared) мутируемому состоянию (mutable state).

В целом состояние объекта — это его данные, хранящиеся в переменных состояния (state variables), таких как экземплярные и статические поля или поля из других зависимых объектов. Состояние хеш-массива HashMap частично хранится в самом объекте HashMap, но также и во многих объектах Map.Entry. Состояние объекта включает любые данные, которые могут повлиять на его поведение.

(1) В тексте встречаются термины lock и block, которые часто переводятся одним словом «блокировка», что может подразумевать и объект, и процесс. В английском языке для процесса блокирования как приостановки продвижения есть термин blocking. Под термином lock имеется в виду «замок», «замковый защитный механизм». Во избежание путаницы термин lock переводится, как замок, кроме устоявшихся выражений, где принят перевод «блокировка». Замок — это механизм контроля доступа к данным с целью их защиты. В программировании замки часто используются для того, чтобы несколько программ или программных потоков могли использовать ресурс совместно, например, обращаться к файлу для его обновления на поочередной основе. — Примеч. науч. ред.

К совместной переменной могут обратиться несколько потоков, мутируемая — меняет свое значение. На самом деле мы пытаемся защитить от неконтролируемого конкурентного доступа не код, а данные.

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

Всякий раз, когда более чем один поток обращается к переменной состояния и один из потоков, возможно, в нее пишет, все потоки должны координировать свой доступ к ней с помощью синхронизации. Синхронизацию в Java обеспечивают ключевое слово synchronized, дающее эксклюзивную блокировку, а также волатильные (volatile) и атомарные переменные и явные замки.

Удержитесь от соблазна думать, что существуют ситуации, не требующие синхронизации. Программа может работать и проходить свои тесты, но оставаться неисправной и завершиться аварийно в любой момент.

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

Будут или нет многочисленные потоки обращаться к той или иной переменной, узнать сложно. К счастью, объектно‑ориентированные технические решения, которые помогают создавать хорошо организованные и удобные в сопровождении классы — такие как инкапсуляция и сокрытие данных, — также помогают создавать потокобезопасные классы. Чем меньше потоков имеет доступ к определенной переменной, тем проще обеспечить синхронизацию и задать условия, при которых к данной переменной можно обращаться. Язык Java не заставляет вас инкапсулировать состояние — вполне допустимо хранить состояние в публичных полях (даже публичных статических полях) или публиковать ссылку на объект, который в иных случаях является внутренним, — но чем лучше инкапсулировано состояние вашей программы, тем проще сделать вашу программу потокобезопасной и помочь сопроводителям поддерживать ее в таком виде.

При проектировании потокобезопасных классов хорошие объектно‑ориентированные технические решения: инкапсуляция, немутируемость и четкая спецификация инвариантов — будут вашими помощниками.

Если хорошие объектно‑ориентированные проектные технические решения расходятся с потребностями разработчика, стоит поступиться правилами хорошего проектирования ради производительности либо обратной совместимости с устаревшим кодом. Иногда абстракция и инкапсуляция расходятся с производительностью — хотя и не так часто, как считают многие разработчики, — но образцовая практика состоит в том, чтобы сначала делать код правильным, а затем — быстрым. Старайтесь задействовать оптимизацию только в том случае, если измерения производительности и потребности говорят о том, что вы обязаны это сделать (2).

Читайте также:  у новорожденного киста сплетения сосудов головного мозга

(2) В конкурентном коде следует придерживаться этой практики даже больше, чем обычно. Поскольку ошибки конкурентности чрезвычайно трудно воспроизводимы и не просты в отладке, преимущество небольшого прироста производительности на некоторых редко используемых ветвях кода может вполне оказаться ничтожным по сравнению с риском, что программа завершится аварийно в условиях эксплуатации.

Если вы решите, что вам необходимо нарушить инкапсуляцию, то не все потеряно. Вашу программу по‑прежнему можно сделать потокобезопасной, но процесс будет сложнее и дороже, а результат — ненадежнее. Глава 4 характеризует условия, при которых можно безопасно смягчать инкапсуляцию переменных состояния.

До сих пор мы использовали термины «потокобезопасный класс» и «потокобезопасная программа» почти взаимозаменяемо. Строится ли потокобезопасная программа полностью из потокобезопасных классов? Не обязательно: программа, которая состоит полностью из потокобезопасных классов, может не быть потокобезопасной, и потокобезопасная программа может содержать классы, которые не являются потокобезопасными. Вопросы, связанные с компоновкой потокобезопасных классов, также рассматриваются в главе 4. В любом случае понятие потокобезопасного класса имеет смысл только в том случае, если класс инкапсулирует собственное состояние. Термин «потокобезопасность» может применяться к коду, но он говорит о состоянии и может применяться только к тому массиву кода, который инкапсулирует его состояние (это может быть объект или вся программа целиком).

2.1. Что такое потокобезопасность?

Дать определение потокобезопасности непросто. Быстрый поиск в Google выдает многочисленные варианты, подобные этим:

… может вызываться из многочисленных потоков программы без нежелательных взаимодействий между потоками.

… может вызываться двумя или более потоками одновременно, не требуя никаких других действий с вызывающей стороны.

Учитывая подобные определения, неудивительно, что мы находим потокобезопасность запутанной! Как отличить потокобезопасный класс от небезопасного? Что мы вообще подразумеваем под словом «безопасный»?

В основе любого разумного определения потокобезопасности лежит понятие правильности (correctness).

Правильность подразумевает соответствие класса своей спецификации. Спецификация определяет инварианты (invariants), ограничивающие состояние объекта, и постусловия (postconditions), описывающие эффекты от операций. Как узнать, что спецификации для классов являются правильными? Никак, но это не мешает нам их использовать после того, как мы убедили себя, что код работает. Поэтому давайте допустим, что однопоточная правильность — это нечто видимое. Теперь можно предположить, что потокобезопасный класс ведет себя правильно во время доступа из многочисленных потоков.

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

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

(3) Если нестрогое использование термина правильность здесь вас беспокоит, то вы можете думать о потокобезопасном классе как о классе, который неисправен в конкурентной среде, как и в однопоточной среде.

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

2.1.1. Пример: сервлет без поддержки внутреннего состояния

В главе 1 мы перечислили структуры, которые создают потоки и вызывают из них компоненты, за потокобезопасность которых ответственны вы. Теперь мы намерены разработать сервлетную службу разложения на множители и постепенно расширить ее функционал, сохраняя потокобезопасность.

В листинге 2.1 показан простой сервлет, который распаковывает число из запроса, раскладывает его на множители и упаковывает результаты в отклик.

Листинг 2.1. Сервлет без поддержки внутреннего состояния

Класс StatelessFactorizer, как и большинство сервлетов, не имеет внутреннего состояния: не содержит полей и не ссылается на поля из других классов. Состояние для конкретного вычисления существует только в локальных переменных, которые хранятся в потоковом стеке и доступны только для выполняющего потока. Один поток, обращающийся к StatelessFactorizer, не может повлиять на результат другого потока, делающего то же самое, поскольку эти потоки не используют состояние совместно.

Объекты без поддержки внутреннего состояния всегда являются потокобезопасными.

Тот факт, что большинство сервлетов могут быть реализованы без поддержки внутреннего состояния, значительно снижает бремя по обеспечению потокобезопасности самих сервлетов. И только когда сервлеты должны что-то запомнить, требования к их потокобезопасности возрастают.

2.2. Атомарность

Что происходит при добавлении элемента состояния в объект без поддержки внутреннего состояния? Предположим, мы хотим добавить счетчик посещений, который измеряет число обработанных запросов. Можно добавить в сервлет поле с типом long и приращивать его при каждом запросе, как показано в UnsafeCountingFactorizer в листинге 2.2.

Листинг 2.2. Сервлет, подсчитывающий запросы без необходимой синхронизации. Так делать не следует

К сожалению, класс UnsafeCountingFactorizer не является потокобезопасным, даже если отлично работает в однопоточной среде. Так же, как UnsafeSequence, он предрасположен к потерянным обновлениям (lost updates). Хотя операция приращения ++count имеет компактный синтаксис, она не является атомарной (atomic), то есть неделимой, а представляет собой последовательность из трех операций: доставки текущего значения, прибавления к нему единицы и записи нового значения обратно. В операциях «прочитать, изменить, записать» результирующее состояние является производным от предыдущего.

На рис. 1.1 показано, что может произойти, если два потока попытаются увеличить счетчик одновременно, без синхронизации. Если счетчик равен 9, то из‑за неудачной временной координации оба потока увидят значение 9, добавят в него единицу, и установят значение 10. Так счетчик посещений начнет отставать на единицу.

Вы можете подумать, что наличие немного неточного счетчика посещений в веб‑службе является приемлемой потерей, и иногда это так. Но если счетчик используется для создания последовательностей или уникальных идентификаторов объектов, то возвращение одного и того же значения из многочисленных активаций может привести к серьезным проблемам целостности данных. Возможность появления неправильных результатов из-за неудачной временнˆой координации возникает при состоянии гонки.

Читайте также:  учение о развитии природы и общества в марксизме является

2.2.1. Состояния гонки

Класс UnsafeCountingFactorizer имеет несколько состояний гонки (4). Наиболее распространенным типом состояния гонки является ситуация «проверить и затем действовать», где потенциально устаревшее наблюдение используется для принятия решения о том, что делать дальше.

(4) Термин состояние гонки часто путают с родственным термином гонка данных (data race). Гонка данных возникает, когда синхронизация не используется для координации всего доступа к общему нефинальному полю. Вы рискуете попасть в гонку данных всякий раз, когда поток пишет переменную, которая затем может быть прочитана другим потоком, либо считывает переменную, которая в последний раз могла быть записана другим потоком, если оба потока не используют синхронизацию. Код с гонками данных не имеет полезной формально определенной семантики в рамках модели памяти Java. Не все состояния гонки являются гонками данных, и не все гонки данных являются состояниями гонки, но оба типа ситуаций могут вызывать аварийный сбой конкурентных программ самым непредсказуемым образом. UnsafeCountingFactorizer содержит оба типа. Подробнее гонки данных описаны в главе 16.

Мы часто сталкиваемся с состоянием гонки в реальной жизни. Допустим, вы планируете встретиться с другом в полдень в кафе «Старбакс» на Университетском проспекте. Но вы узнаете, что на Университетском проспекте находятся два «Старбакса». В 12:10 вы не видите своего друга в кафе A и идете в кафе B, но там его тоже нет. Либо ваш друг опаздывает, либо он прибыл в кафе A сразу после того, как вы ушли, либо он был в кафе B, но пошел вас искать и теперь находится на пути к кафе A. Примем последний, то есть самый худший вариант. Сейчас 12:15, и вы оба задаетесь вопросом, а сдержал ли друг обещание. Вы вернетесь в другое кафе? Сколько раз вы будете ходить туда и обратно? Если вы не согласовали протокол, то можете провести весь день, гуляя по Университетскому проспекту в кофеиновой эйфории.
Проблема подхода «прогуляться и посмотреть, не находится ли он там» заключается в том, что прогулка по улице между двумя кафе занимает несколько минут, и за это время состояние системы может измениться.

Пример со «Старбаксом» иллюстрирует зависимость результата от относительной временной координации событий (от того, как долго вы ждете друга, находясь в кафе, и т. д.). Наблюдение, что он не находится в кафе A, становится потенциально утратившим силу: как только вы выходите из парадной двери, он может войти через заднюю дверь. Большинство состояний гонки вызывают такие проблемы, как неожиданное исключение, перезаписанные данные и повреждение файла.

2.2.2. Пример: состояния гонки в ленивой инициализации

Распространенным приемом, использующим подход «проверить и затем действовать», является ленивая инициализация (LazyInitRace). Ее цель — отложить инициализацию объекта до тех пор, пока он не понадобится, и обеспечить, чтобы он инициализировался только один раз. В листинге 2.3 метод getInstance убеждается в выполнении инициализации ExpensiveObject и возвращает существующий экземпляр, или, в противном случае, создает новый экземпляр и возвращает его после сохранения ссылки на него.

Листинг 2.3. Состояние гонки в ленивой инициализации. Так делать не следует

Класс LazyInitRace содержит состояния гонки. Предположим, что потоки A и B выполняют метод getInstance в одно и то же время. A видит, что поле instance равно null, и создает новый ExpensiveObject. Поток B также проверяет, равно ли поле instance тому же значению null. Наличие в поле значения null в этот момент зависит от временнˆой координации, включая капризы планирования и количество времени, нужного для создания экземпляра объекта ExpensiveObject и установки значения в поле instance. Если поле instance равно null, когда B его проверяет, два элемента кода, вызывающих метод getInstance, могут получить два разных результата, даже если метод getInstance предположительно должен всегда возвращать один и тот же экземпляр.

Счетчик посещений в UnsafeCountingFactorizer тоже содержит состояния гонки. Подход «прочитать, изменить, записать» подразумевает, что для приращения счетчика поток должен знать его предыдущее значение и убедиться, что в процессе обновления никто другой не изменяет и не использует это значение.

Как и большинство ошибок конкурентности, состояния гонки не всегда приводят к сбою: временная координация бывает удачной. Но если класс LazyInitRace используется для инстанциации реестра всего приложения, то, когда из многочисленных активаций он будет возвращать разные экземпляры, регистрации будут утеряны либо действия получат противоречивые представления набора зарегистрированных объектов. Или если класс UnsafeSequence используется для генерирования идентификаторов сущностей в структуре консервации данных, то два разных объекта могут иметь один и тот же идентификатор, нарушая ограничения идентичности.

2.2.3. Составные действия

И LazyInitRace, и UnsafeCountingFactorizer содержат последовательность операций, которые должны быть атомарными. Но для предотвращения состояния гонки должно существовать препятствие тому, чтобы другие потоки использовали переменную, пока один поток ее изменяет.

Операции A и B являются атомарными, если, с точки зрения потока, выполняющего операцию A, операция B либо была целиком выполнена другим потоком, либо не выполнена даже частично.

Атомарность операции приращения в UnsafeSequence позволила бы избежать состояния гонки, показанного на рис. 1.1. Операции «проверить и затем действовать» и «прочитать, изменить, записать» всегда должны быть атомарными. Они называются составными действиями (сompound actions) — последовательностями операций, которые должны выполняться атомарно, для того чтобы оставаться потокобезопасными. В следующем разделе мы рассмотрим блокировку — встроенный в Java механизм, который обеспечивает атомарность. А пока мы исправим проблему другим способом, применив существующий потокобезопасный класс, как показано в Countingfactorizer в листинге 2.4.

Листинг 2.4. Сервлет, подсчитывающий запросы с помощью AtomicLong

Пакет java.util.concurrent.atomic содержит атомарные переменные (atomic variable) для управления состояниями классов. Заменив тип счетчика с long на AtomicLong, мы гарантируем, что все действия, которые обращаются к состоянию счетчика, являются атомарными1. Поскольку состояние сервлета является состоянием счетчика, а счетчик является потокобезопасным, наш сервлет становится потокобезопасным.

При добавлении единственного элемента состояния в класс, который не поддерживает внутреннее состояние, результирующий класс будет потокобезопасным, если состояние полностью управляется потокобезопасным объектом. Но, как мы увидим в следующем разделе, переход от одной переменной состояния к следующим будет не так прост, как переход от нуля к единице.

Там, где это удобно, используйте существующие потокобезопасные объекты, такие как AtomicLong, для управления состоянием вашего класса. Возможные состояния существующих потокобезопасных объектов и их переходы в другие состояния легче поддерживать и проверять на потокобезопасность, нежели произвольные переменные состояния.

Для Хаброжителей скидка 25% по купону — Java

По факту оплаты бумажной версии книги на e-mail высылается электронная книга.

Источник

Беременность и дети