Синхронизированные секции должны иметь минимальный размер
Ключевое слово synchronized устанавливает блокировку. Все секции кода, защищенные одной блокировкой, в любой момент времени гарантированно выполняются только в одном программном потоке. Блокировки обходятся дорого, так как они создают задержки и увеличивают затраты ресурсов. Следовательно, код не должен перегружаться лишними конструкциями synchronized. С другой стороны, все критические секции[62] должны быть защищены. Следовательно, код должен содержать как можно меньше критических секций.
Для достижения этой цели некоторые наивные программисты делают свои критические секции очень большими. Однако синхронизация за пределами минимальных критических секций увеличивает конкуренцию между потоками и снижает производительность[63].
Рекомендация:
О трудности корректного завершения
Написание системы, которая должна работать бесконечно, заметно отличается от написания системы, которая работает в течение некоторого времени, а затем корректно завершается.
Реализовать корректное завершение порой бывает весьма непросто. Одна из типичных проблем — взаимная блокировка[64] программных потоков, бесконечно долго ожидающих сигнала на продолжение работы.
Представьте систему с родительским потоком, который порождает несколько дочерних потоков, а затем дожидается их завершения, чтобы освободить свои ресурсы и завершиться. Что произойдет, если один из дочерних потоков попадет во взаимную блокировку? Родитель будет ожидать вечно, и система не сможет корректно завершиться.
Или возьмем аналогичную систему, получившую сигнал о завершении. Родитель приказывает всем своим потомкам прервать свои операции и завершить работу. Но что если два потомка составляют пару «производитель/потребитель»? Допустим, производитель получает сигнал от родителя, и прерывает свою работу. Потребитель, в этот момент ожидавший сообщения от производителя, блокируется в состоянии, в котором он не может получить сигнал завершения. В результате он переходит в бесконечное ожидание — а значит, родитель тоже не сможет завершиться.
Подобные ситуации вовсе не являются нетипичными. Если вы пишете многопоточный код, который должен корректно завершаться, не жалейте времени на обеспечение нормального завершения работы.
Рекомендация:
Тестирование многопоточного кода
Тестирование не гарантирует правильности работы кода. Тем не менее качественное тестирование сводит риск к минимуму. Для однопоточных решений эти утверждения безусловно верны. Но как только в системе появляются два и более потока, использующие общий код и работающих с общими данными, ситуация значительно усложняется.
Рекомендация:
Несколько более конкретных рекомендаций:
• Рассматривайте непериодические сбои как признаки возможных проблем многопоточности.
• Начните с отладки основного кода, не связанного с многопоточностью.
• Реализуйте логическую изоляцию конфигураций многопоточного кода.
• Обеспечьте возможность настройки многопоточного кода.
• Протестируйте программу с количеством потоков, превышающим количество процессоров.
• Протестируйте программу на разных платформах.
• Применяйте инструментовку кода для повышения вероятности сбоев.
Рассматривайте непериодические сбои как признаки возможных проблем многопоточности