Главная » Разработка для Android » Синхронизация и потоковая безопасность – JAVA ДЛЯ ANDROID

0

 

Когда два или более работающих потока имеют доступ к одному и тому же набору переменных, потоки могут изменять эти переменные, причем так, что возможно повреждение данных или нарушение логики одного или нескольких из этих потоков. Подобные ненамеренные ошибки конкурентного доступа называются нарушениями безопасности потоков (thread safety violations). Их сложно воспроизводить, сложно находить и сложно тестировать.

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

Можно подумать: «Нет никакой необходимости синхронизировать переменную shouldStop. Действительно, при доступе к ней между основным и порожденным потоком может возникнуть конфликт. И что? Через секунду порожденный поток обязательно установит значение true. Булевы выражения являются атомарными. Если основной поток на данный момент не увидит, что порожденный поток имеет значение true, то, несомненно, он увидит порожденный поток в значении true в следующий раз». Эта логика не только ложная, но и опасная. Она не учитывает действия оптимизирующих компиляторов и каптирующих процессоров! Программа может никогда не закончиться. Каждый из двух потоков вполне может использовать собственную копию shouldStop, существующую только в аппаратном кэше какого-нибудь локального процессора. Поскольку синхронизация между двумя потоками отсутствует, копия кэша, возможно, так и не будет предоставлена в общий доступ и основной поток никогда не увидит значения порожденного потока.

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

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

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

Ключевое слово synchroni zed может применяться в трех контекстах: для создания блока, для работы со статическим методом или для работы с динамическим методом. При определении блока это ключевое слово принимает в качестве аргумента ссылку на объект, который будет использоваться в качестве семафора. Примитивные типы не могут применяться в качестве семафоров, а любой объект – может.

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

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

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

Наконец, следует отметить, что объектные блокировки в Java являются многоразовыми (reentrant). Следующий код совершенно безопасен и не вызывает взаимоблокировки (клинча):

Источник: Android. Программирование на Java для нового поколения мобильных устройств

По теме:

  • Комментарии