Главная страница
Навигация по странице:

  • Рекомендация

  • Следствие: потоки должны быть как можно более независимы

  • Потоково-безопасные коллекции

  • Модель «производители-потребители»

  • Модель «обедающих философов»

  • Остерегайтесь зависимостей между синхронизированными методами

  • Синхронизированные секции должны иметь минимальный размер

  • Рассматривайте непериодические сбои как признаки возможных проблем многопоточности

  • Реализуйте переключение конфигураций многопоточного кода

  • Создание, анализ ирефакторинг


    Скачать 3.16 Mb.
    НазваниеСоздание, анализ ирефакторинг
    Дата29.09.2022
    Размер3.16 Mb.
    Формат файлаpdf
    Имя файлаChistyj_kod_-_Sozdanie_analiz_i_refaktoring_(2013).pdf
    ТипКнига
    #706087
    страница25 из 49
    1   ...   21   22   23   24   25   26   27   28   ...   49
    212
    Глава 13 . Многопоточность исходят обращения к общему объекту, ключевым словом synchronized
    . Количество критических секций в коде должно быть сведено к минимуму . Чем больше в про- грамме мест, в которых обновляются общие данные, тем с большей вероятностью:
    
    вы забудете защитить одно или несколько из этих мест, что приведет к на- рушению работы всего кода, изменяющего общие данные .
    
    попытки уследить за тем, чтобы все было надежно защищено, приведут к дуб- лированию усилий (нарушение принципа DRY [PRAG]) .
    Вам будет труднее определить источник многопоточных сбоев, который и так достаточно сложно найти .
    Рекомендация: серьезно относитесь к инкапсуляции данных; жестко ограничьте
    доступ ко всем общим данным.
    Следствие: используйте копии данных
    Как избежать нежелательных последствий одновременного доступа к данным?
    Например, просто не использовать его . Существуют разные стратегии: напри- мер, в одних ситуациях можно скопировать общий объект и ограничить доступ к копии (доступ только для чтения) . В других ситуациях объекты копируются, результаты работы нескольких программных потоков накапливаются в копиях, а затем объединяются в одном потоке .
    Если существует простой способ избежать одновременного доступа к объектам, то вероятность возникновения проблем в полученном коде значительно снижает- ся . Вас беспокоят затраты на создание лишних объектов? Поэкспериментируйте и выясните, действительно ли она так высока . Как правило, если копирование объектов позволяет избежать синхронизации в коде, экономия на защитных блокировках быстро окупит дополнительные затраты на создание объектов и уборку мусора .
    Следствие: потоки должны быть как можно
    более независимы
    Постарайтесь писать многопоточный код так, чтобы каждый поток существовал в собственном замкнутом пространстве и не использовал данные совместно с дру- гими процессами . Каждый поток обрабатывает один клиентский запрос, все его данные берутся из отдельного источника и хранятся в локальных переменных .
    В этом случае каждый поток работает так, словно других потоков не существует, а следовательно, нет и требований к синхронизации .
    Например, классы, производные от
    HttpServlet
    , получают всю информацию в параметрах, передаваемых методам doGet и doPost
    . В результате каждый сервлет действует так, словно в его распоряжении находится отдельный компьютер . Если код сервлета ограничивается одними локальными переменными, он ни при каких условиях не вызовет проблем синхронизации . Конечно, большинство приложе-
    212

    Знайте свою библиотеку
    213
    ний, использующих сервлеты, рано или поздно сталкиваются с использованием общих ресурсов — например, подключений к базам данных .
    Рекомендация: постарайтесь разбить данные не независимые подмножества, с ко- торыми могут работать независимые потоки (возможно, на разных процессорах) .
    Знайте свою библиотеку
    В Java 5 возможности многопоточной разработки были значительно расширены по сравнению с предыдущими версиями . При написании многопоточного кода в Java 5 следует руководствоваться следующими правилами:
    
    Используйте потоково-безопасные коллекции .
    
    Используйте механизм Executor Framework для выполнения несвязанных задач .
    
    По возможности используйте неблокирующие решения .
    
    Некоторые библиотечные классы не являются потоково-безопасными .
    Потоково-безопасные коллекции
    Когда язык Java был еще молод, Даг Ли написал основополагающую книгу
    «Concurrent Programming in Java» [Lea99] . В ходе работы над книгой он разра- ботал несколько потоково-безопасных коллекций, которые позднее были вклю- чены в JDK в пакете java.util.concurrent
    . Коллекции этого пакета безопасны в условиях многопоточного выполнения, к тому же они достаточно эффективно работают . Более того, реализация
    ConcurrentHashMap почти всегда работает лучше
    HashMap
    . К тому же она поддерживает возможность выполнения параллельных операций чтения и записи и содержит методы для выполнения стандартных со- ставных операций, которые в общем случае не являются потоково-безопасными .
    Если ваша программа будет работать в среде Java 5, используйте
    ConcurrentHashMap в разработке .
    Также в Java 5 были добавлены другие классы для поддержки расширенной многопоточности . Несколько примеров .
    ReentrantLock
    Блокировка, которая может устанавливаться и освобождаться в разных методах
    Semaphore
    Реализация классического семафора (блокировка со счетчиком)
    CountDownLatch
    Блокировка, которая ожидает заданного количества событий до освобож- дения всех ожидающих потоков. Позволяет организовать более или менее одновременный запуск нескольких потоков
    Рекомендация: изучайте доступные классы. Если вы работаете на Java, уделите
    особое внимание пакетам
    java.util.concurrent
    , java.util.concurrent.atomic
    и
    java.
    util.concurrent.locks
    213

    214
    Глава 13 . Многопоточность
    Знайте модели выполнения
    В многопоточных приложениях возможно несколько моделей логического раз- биения поведения программы . Но чтобы понять их, необходимо сначала позна- комиться с некоторыми базовыми определениями .
    Связанные ресурсы
    Ресурсы с фиксированным размером или количеством, существующие в многопоточной среде, например подключения к базе данных или бу- феры чтения/записи
    Взаимное исключение В любой момент времени с общими данными или с общим ресурсом может работать только один поток
    Зависание
    Работа одного или нескольких потоков приостанавливается на слишком долгое время (или навсегда). Например, если высокоприоритетным по- токам всегда предоставляется возможность отработать первыми, то низ- коприоритетные потоки зависнут (при условии, что в системе постоянно появляются новые высокоприоритетные потоки)
    Взаимная блокировка
    (deadlock)
    Два и более потока бесконечно ожидают завершения друг друга. Каждый поток захватил ресурс, необходимый для продолжения работы другого потока, и ни один поток не может завершиться без получения захвачен- ного другим потоком ресурса
    Обратимая блокировка
    1
    (livelock)
    Потоки не могут «разойтись» — каждый из потоков пытается выполнять свою работу, но обнаруживает, что другой поток стоит у него на пути. По- токи постоянно пытаются продолжить выполнение, но им это не удается в течение слишком долгого времени (или вообще не удается)
    Вооружившись этими определениями, можно переходить к обсуждению различ- ных моделей выполнения, встречающихся в многопоточном программировании .
    Модель «производители-потребители»
    1
    Один или несколько потоков-производителей создают задания и помещают их в буфер или очередь . Один или несколько потоков-потребителей извлекают задания из очереди и выполняют их . Очередь между производителями и по- требителями является связанным ресурсом . Это означает, что производители перед записью должны дожидаться появления свободного места в очереди, а по- требители должны дожидаться появления заданий в очереди для обработки .
    Координация производителей и потребителей основана на передаче сигналов .
    Производитель записывает задание и сигнализирует о том, что очередь не пуста .
    Потребитель читает задание и сигнализирует о том, что очередь не заполнена .
    Обе стороны должны быть готовы ожидать оповещения о возможности про- должения работы .
    1
    Также встречается термин «активная блокировка» . — Примеч. перев .
    2
    http://en .wikipedia .org/wiki/Producer-consumer
    214

    Знайте модели выполнения
    215
    Модель «читатели-писатели»
    1
    Если в системе имеется общий ресурс, который в основном служит источни- ком информации для потоков-«читателей», но время от времени обновляется потоками-«писателями», на первый план выходит проблема оперативности обновления . Если обновление будет происходить недостаточно часто, это может привести к зависанию и накоплению устаревших данных . С другой стороны, слишком частые обновления влияют на производительность . Координация работы читателей так, чтобы они не пытались читать данные, обновляемые пи- сателями, и наоборот, — весьма непростая задача . Писатели обычно блокируют работу многих читателей в течение долгого периода времени, а это отражается на производительности .
    Проектировщик должен найти баланс между потребностями читателей и писате- лей, чтобы обеспечить правильный режим работы, нормальную производитель- ность системы и избежать зависания . В одной из простых стратегий писатели дожидаются, пока в системе не будет ни одного читателя, и только после этого выполняют обновление . Однако при постоянном потоке читателей такая страте- гия приведет к зависанию писателей . С другой стороны, при большом количестве высокоприоритетных писателей пострадает производительность . Поиск баланса и предотвращение ошибок многопоточного обновления — основные проблемы этой модели выполнения .
    Модель «обедающих философов»
    2
    Представьте нескольких философов, сидящих за круглым столом . Слева у каж- дого философа лежит вилка, а в центре стола стоит большая тарелка спагетти .
    Философы проводят время в размышлениях, пока не проголодаются . Проголо- давшись, философ берет вилки, лежащие по обе стороны, и приступает к еде . Для еды необходимы две вилки . Если сосед справа или слева уже использует одну из необходимых вилок, философу приходится ждать, пока сосед закончит есть и положит вилки на стол . Когда философ поест, он кладет свои вилки на стол и снова погружается в размышления .
    Заменив философов программными потоками, а вилки — ресурсами, мы получа- ем задачу, типичную для многих корпоративных систем, в которых приложения конкурируют за ресурсы из ограниченного набора . Если небрежно отнестись к проектированию такой системы, то конкуренция между потоками может при- вести к возникновению взаимных блокировок, обратимых блокировок, падению производительности и эффективности работы .
    Большинство проблем многопоточности, встречающихся на практике, обычно представляют собой те или иные разновидности этих трех моделей . Изучайте
    1
    http://en .wikipedia .org/wiki/Readers-writers_problem
    2
    http://en .wikipedia .org/wiki/Dining_philosophers_problem
    215

    216
    Глава 13 . Многопоточность алгоритмы, самостоятельно создавайте их реализации, чтобы столкнувшись с этими проблемами, вы были готовы к их решению .
    Рекомендация: изучайте базовые алгоритмы, разбирайтесь в решениях.
    Остерегайтесь зависимостей
    между синхронизированными методами
    Зависимости между синхронизированными методами приводят к появлению коварных ошибок в многопоточном коде . В языке Java существует ключевое слово synchronized для защиты отдельных методов . Но если общий класс со- держит более одного синхронизированного метода, возможно, ваша система спроектирована неверно
    1
    Рекомендация: избегайте использования нескольких методов одного совместно
    используемого объекта.
    Впрочем, иногда без использования разных методов одного общего объекта обой- тись все же не удается . Для обеспечения правильности работы кода в подобных ситуациях существуют три стандартных решения:
    
    Блокировка на стороне клиента — клиент устанавливает блокировку для сервера перед вызовом первого метода и следит за тем, чтобы блокировка распространялась на код, вызывающий последний метод .
    
    Блокировка на стороне сервера — на стороне сервера создается метод, который блокирует сервер, вызывает все методы, после чего снимает блокировку . Этот новый метод вызывается клиентом .
    
    Адаптирующий сервер — в системе создается посредник, который реализует блокировку . Ситуация может рассматриваться как пример блокировки на стороне сервера, в которой исходный сервер не может быть изменен .
    Синхронизированные секции
    должны иметь минимальный размер
    Ключевое слово synchronized устанавливает блокировку . Все секции кода, за- щищенные одной блокировкой, в любой момент времени гарантированно вы- полняются только в одном программном потоке . Блокировки обходятся дорого, так как они создают задержки и увеличивают затраты ресурсов . Следовательно, код не должен перегружаться лишними конструкциями synchronized
    . С другой
    1
    См . раздел «Зависимости между методами могут нарушить работу многопоточного кода», с . 370 .
    216

    О трудности корректного завершения
    217
    стороны, все критические секции
    1
    должны быть защищены . Следовательно, код должен содержать как можно меньше критических секций .
    Для достижения этой цели некоторые наивные программисты делают свои кри- тические секции очень большими . Однако синхронизация за пределами мини- мальных критических секций увеличивает конкуренцию между потоками и сни- жает производительность
    2
    Рекомендация: синхронизированные секции в ваших программах должны иметь
    минимальные размеры.
    О трудности корректного завершения
    Написание системы, которая должна работать бесконечно, заметно отличается от написания системы, которая работает в течение некоторого времени, а затем корректно завершается .
    Реализовать корректное завершение порой бывает весьма непросто . Одна из типичных проблем — взаимная блокировка
    3
    программных потоков, бесконечно долго ожидающих сигнала на продолжение работы .
    Представьте систему с родительским потоком, который порождает несколько дочерних потоков, а затем дожидается их завершения, чтобы освободить свои ресурсы и завершиться . Что произойдет, если один из дочерних потоков попадет во взаимную блокировку? Родитель будет ожидать вечно, и система не сможет корректно завершиться .
    Или возьмем аналогичную систему, получившую сигнал о завершении . Родитель приказывает всем своим потомкам прервать свои операции и завершить работу . Но что если два потомка составляют пару «производитель/потребитель»? Допустим, производитель получает сигнал от родителя, и прерывает свою работу . Потреби- тель, в этот момент ожидавший сообщения от производителя, блокируется в состо- янии, в котором он не может получить сигнал завершения . В результате он пере- ходит в бесконечное ожидание — а значит, родитель тоже не сможет завершиться .
    Подобные ситуации вовсе не являются нетипичными . Если вы пишете много- поточный код, который должен корректно завершаться, не жалейте времени на обеспечение нормального завершения работы .
    Рекомендация: начинайте думать о корректном завершении на ранней ста-
    дии разработки. На это может уйти больше времени, чем вы предполагаете.
    Проанализируйте существующие алгоритмы, потому что эта задача сложнее,
    чем кажется .
    1
    «Критической секцией» называется любой фрагмент кода, который должен быть защищен от одновременного использования несколькими программными потоками .
    2
    См . раздел «Увеличение производительности», с . 375 .
    3
    См . раздел «Взаимная блокировка», с . 377 .
    217

    218
    Глава 13 . Многопоточность
    тестирование многопоточного кода
    Тестирование не гарантирует правильности работы кода . Тем не менее каче- ственное тестирование сводит риск к минимуму . Для однопоточных решений эти утверждения безусловно верны . Но как только в системе появляются два и более потока, использующие общий код и работающих с общими данными, ситуация значительно усложняется .
    Рекомендация: пишите тесты, направленные на выявление существующих
    проблем. Часто выполняйте их для разных вариантов программных/системных
    конфигураций и уровней нагрузки. Если при выполнении теста происходит ошибка,
    обязательно найдите причину. Не игнорируйте ошибку только потому, что при
    следующем запуске тест был выполнен успешно.
    Несколько более конкретных рекомендаций:
    
    Рассматривайте непериодические сбои как признаки возможных проблем многопоточности .
    
    Начните с отладки основного кода, не связанного с многопоточностью .
    
    Реализуйте логическую изоляцию конфигураций многопоточного кода .
    
    Обеспечьте возможность настройки многопоточного кода .
    
    Протестируйте программу с количеством потоков, превышающим количество процессоров .
    
    Протестируйте программу на разных платформах .
    
    Применяйте инструментовку кода для повышения вероятности сбоев .
    Рассматривайте непериодические сбои
    как признаки возможных проблем
    многопоточности
    В многопоточном коде сбои происходят даже там, где их вроде бы и быть не может . Многие разработчики (в том числе и автор) не обладают интуитивным представлением о том, как многопоточный код взаимодействует с другим кодом .
    Ошибки в многопоточном коде могут проявляться один раз за тысячу или даже миллион запусков . Воспроизвести такие ошибки в системе бывает очень трудно, поэтому разработчики часто склонны объяснять их «фазами Луны», случайными сбоями оборудования или другими несистематическими причинами . Однако игнорируя существование этих «разовых» сбоев, вы строите свой код на потен- циально ненадежном фундаменте .
    Рекомендация: не игнорируйте системные ошибки, считая их случайными, разо-
    выми сбоями.
    218

    Тестирование многопоточного кода
    219
    начните с отладки основного кода,
    не связанного с многопоточностью
    На первый взгляд совет выглядит тривиально, но еще раз подчеркнуть его зна- чимость не лишне . Убедитесь в том, что сам код работает вне многопоточного контекста . В общем случае это означает создание POJO-объектов, вызываемых из потоков . POJO-объекты не обладают поддержкой многопоточности, а следо- вательно, могут тестироваться вне многопоточной среды . Чем больше системного кода можно разместить в таких POJO-объектах, тем лучше .
    Рекомендация: не пытайтесь одновременно отлавливать ошибки в обычном и многопоточном коде . Убедитесь в том, что ваш код работает за пределами многопоточной среды выполнения .
    Реализуйте переключение конфигураций
    многопоточного кода
    Напишите вспомогательный код поддержки многопоточности, который может работать в разных конфигурациях .
    
    Один поток; несколько потоков; количество потоков изменяется по ходу вы- полнения .
    
    Многопоточный код взаимодействует с реальным кодом или тестовыми за- менителями .
    
    Код выполняется с тестовыми заменителями, которые работают быстро; мед- ленно; с переменной скоростью .
    
    Настройте тесты таким образом, чтобы они могли выполняться заданное количество раз .
    1   ...   21   22   23   24   25   26   27   28   ...   49


    написать администратору сайта