При написании многопоточных программ следует придерживаться определённых правил, которые помогают обеспечить достойную производительность приложения в сочетании с удобной отладкой и простотой дальнейшей поддержки кода.
- Всегда давайте значимые имена своим потокам. Процесс отладки, нахождения ошибок или отслеживание исключения в многопоточном коде – довольно сложная задача.
OrderProcessor
,QuoteProcessor
илиTradeProcessor
намного информативнее, чемThread1
,Thread2
иThread3
. Имя должно отражать задачу, выполняемую данным потоком. - Избегайте блокировок или старайтесь уменьшить масштабы синхронизации. Блокировка затратна, а переключение контекста ещё более ресурсоёмко. Пытайтесь избегать синхронизации и блокировки насколько это возможно, и организуйте критическую секцию в минимально необходимом объёме. Поэтому синхронизированный блок всегда предпочительней синхронизированного метода, дополнительно наделяя возможностью абсолютного контроля над масштабом блокировки.
- Обрабатывайте прерывание потока с особой тщательностью. Нет ничего хуже оставшегося заблокированным ресурса или системы в неконстистентном, по причине неподтверждённой транзакции, состоянии.
- Помните об обработке исключений. Выброшенные
InterruptedException
должны быть адекватно обработаны, а не просто подавлены. Так же не стоит пренебрегатьThread.UncaughtExceptionHandler
. При использовании пула потоков необходимо помнить, что он зачастую просто «проглатывает» исключения. Так, если вы отправили на выполнениеRunnable
нужно обязательно поместить код выполнения задачи внутрь блокаtry-catch
. Если в очередь пула помещаетсяCallable
, необходимо удостоверится, что результат выполнения всегда изымается помощью блокирующегоget()
, чтобы в случае возникновения существовала возможнотсь заново выбросить произошедшее исключение. - Между синхронизаторами и
wait()
иnotify()
следует выбирать синхронизаторы. Во-первых, синхронизаторы, типаCountDownLatch
,Semaphore
,CyclicBarrier
илиExchanger
упрощают написание кода. Очень сложно реализовывать комплексный управляющий поток, используяwait()
иnotify()
. Во-вторых, эти классы написаны и поддерживаются настоящими мастерами своего дела и есть шанс, что в последующих версиях JDK они будут оптимизированы изнутри или заменены более производительной внешней реализацией. - Почти всегда использование Concurrent сollection выгоднее использования Synchronized сollection, т.к. первые более современны (используют все доступные на момент их написания новшества языка) и масштабируемы, чем их синхронизированые аналоги.