Разработка сред управляемого исполнения на примере виртуальной машины Java Занятие 4 Салищев С.И. Нити и Планировщик Планировщик Нить приоритет код данные Процессор Создание и разрушение нитей Распределение нагрузки между процессорами Приостановка и возобновление нитей Для синхронизации управляемого кода Для сборки мусора Для синхронизации неуправляемого кода и сборки мусора Синхронизация в Java synchronized отображается в Monitor lock/unlock Методы Object отображаются в операции монитора Монитор lock – захват монитора, ожидание в случае конфликта unlock – освобождение захваченного монитора wait – освобождение монитора и ожидание извещения. Только для захваченного монитора notify – извещение одной ожидающей нити. Только для захваченного монитора notifyAll – извещение всех ожидающих нитей. Только для захваченного монитора При ожидании планировщик не планирует исполнение кода нити При извещении и освобождении монитора нить снова может быть запланирована для исполнения Каузальная модель памяти Java Физически результаты исполнения операций могут переупорядочиваться Одновременным исполнением на разных процессорах Суперскалярным процессором Кешами памяти Оптимизирующим компилятором Важен только порядок регистрации наблюдателем (см. логические часы Лампорта) На одной нити все операции с памятью выполняются в порядке исполнения. Для различных нитей все операции с памятью (кроме long и double) атомарные. ПОРЯДОК НЕ ОПРЕДЕЛЕН! До Java 1.5 упорядочивались только операции синхронизации С Java 1.5 каузальная модель памяти (JLS 3.0, 17.4) Синхронизованные операции Volatile read/write Monitor lock/unlock Синтетические первая и последняя операции нити Запуск исполнения и проверка завершения нити Прерывание нити и обнаружение прерывания нити Существует строгий порядок всех синхронизованных операций в программе согласован с порядком операций каждой нити Отношение синхронизации (synchronizes with) Отношение “синхронизации c” M.unlock со всеми последующими M.lock V.write со всеми последующими V.read Запуск исполнения нити с первой операцией нити Инициализация значения по умолчанию с первой операцией всех нитей Последняя операция нити с обнаружением завершения нити (Thread.isAlive, Thread.Join) Прерывание нити с обнаружением прерывания нити (InterruptedException, Thread.interrupted, Thread.isInterrupted) Отношение предшествования (happens before) Пусть x, y – операции. Будем писать hb(x, y) для обозначения x предшествует y. hb – частичный порядок операций hb(x, y) если x, y исполняются на одной нити и x встречается перед y в естественном порядке исполнения x – последняя операция конструктора объекта, y – первая операция Object.finalize() того же объекта x синхронизована с y hb(x, z) и hb(z, y) – транзитивность запись x предшествует чтению у по семантике final поля (JLS 3.0, 17.5) ВНИМАНИЕ! Если для z не определен порядок относительно x и y, то во время исполнения z порядок исполнения x и у неизвестен. Пример (double check) Неправильно Правильно class A { R r; R getR() { if (r == null) { synchronized { if (r == null) { r = new R(); } } } return r; } } class A { volatile R r; R getR() { if (r == null) { synchronized { if (r == null){ r = new R(); } } } return r; } } Примеры упорядоченности synchronized void m() { notifyAll(); wait(); } notifyAll не извещает следующий wait даже в том же блоке class A { final B b; A() { b = new B(); } } Чтение b возвращает ссылку на полностью инициализированный объект Атомарные операции CAS(addr, old, new) (Atomic Compare and Swap) За одну операцию сравнивает текущее значение со старым и, если совпадает, меняет на новое Если не совпадает, возвращает ошибку Пример: Atomic Increment while (!CAS(&x, x, x + 1)); Write Barrier – сохраняет изменения из кэша процессора в память, ограничивает переупорядочивание записей в будущее Read Barrier – обновляет кэш процессора из памяти, ограничивает переупорядочивание чтений в прошлое Мониторы (fat locks) Object Header Monitor count mutex owner wait lock Thread Thread Thread Thread Thread Thread Заголовок объекта содержит ссылку на структуру монитора либо объект отображается при помощи хеш таблицы Монитор нить владельца счетчик рекурсии синхронизационный ресурс ОS список нитей, ждущих освобождения монитора список нитей, ждущих нотификации Тонкие мониторы (thin locks) Object Header 23 threadId monitorRef 1 fat = 0 fat = 1 lock/unlock x free fat = 0 threadId = 0 count = 0 CAS lock unlock uncontended fat = 0 threadId = x count = y CAS deflate 8 count lock !x inflate contended fat = 1 CAS lock/unlock unlock При чтении используется Read Barrier Сдувание монитора редко реализуется из-за сложности Резервация мониторов Object Header 1 1 22 fat = 0 unreserved threadId fat = 1 monitorRef 8 count lock/unlock x unreserve free to reserve reserved unreserved = 0 CAS unreserved = 0 fat = 0 fat = 0 lock x threadId = 0 threadId = x count = 0 count = у lock !x count > 0 contended fat = 1 uncontended unreserved = 1 fat = 0 threadId = x count = y Операция снятия резервации требует остановки нити владельца, является дорогостоящей Операция снятия резервации может быть выполнена, если нить владелец не запланирована для исполнения, например ожидает в wait Типы планирования Вытесняющее планирование Возможность остановки в любой инструкции Согласовано с планировщиком многозадачных OS Сложно согласовать с GC Кооперативное планирование Остановка в безопасных точках (Yield Point, Safe Point, GC Point) Предпочтительный способ при реализации в ядре OS Требует ожидания перепланировки при остановке нити планировщиком OS, может приводить к задержке Хорошо согласуется с GC Безопасные точки Вызов метода Создание объекта Обратное ветвление Синхронизация Инструкции генерирующие исключение (Potential Exception Instruction) Взаимодействие с GC Расположение ссылок на объекты на стеке и в локальных переменных Требуется при перечислении корневых ссылок Компилятор может использовать регистры Карты ссылок (GC Maps) генерируются компилятором При вытесняющем планировании для каждой инструкции процессора При кооперативном планировании для каждой безопасной точки Правило Гослинга (Gosling property) – для любой точки корректного Java метода карта ссылок не зависит от пути в графе управления, приведшего в эту точку Системные нити 1:1 отображение управляемых и OS нитей Простой планировщик через вызовы OS Любой способ планирования При кооперативном планировании высокая вероятность конфликта с планировщиком OS Требуется синхронизация данных между всеми нитями Структура стека определяется OS Ограничение количества нитей Зеленые (green, logical) нити Простой вид M:N отображения нитей М – управляемые нити, N – нити OS N ≤ P – количество процессоров Кооперативное планирование нитей Планирование по времени может не использоваться. Упрощение системы, но опасность голодания. При реализации планирования по времени усложнение планировщика Уменьшается вероятность небезопасной остановки нитей планировщиком OS. Не требуется синхронизация данных между управляемыми нитями ассоциированными с одним процессором Невозможность использования процессора при системном вызове Любая структура стека Количество нитей не ограничено OS Комбинированные M:N нити Сложное отображение нитей М – управляемые нити, N – нити операционной системы N ≥ P – количество процессоров При системном вызове создается запасная нить ассоциированная с тем же процессором Усложнение синхронизации между нитями OS на одном процессе