Виды блокировок
На данный момент есть три различных видов блокировок: biаsed, thin and fat. Начнем в историческом порядке их появления.1. Fat блокировка
Самый очевидный и простой вариант, основанный на использовании мютекса операционной системы. Применим в любых условиях, включая случай состязательные блокировок в нескольких потоках.2. Thin блокировка
Если же состязательной блокировки практически нет, то можно придумать кое-что побыстрее мьютекса, так как будить в этом случае потоки не нужно и можно обойтись без скедульера операционной системы. Так были придуманы thin блокировки использующие CAS операции для гарантирования эксклюзивного доступа.3. Biased блокировки
На некоторых архитектурах, особенно очень многопроцессорных, CAS операции не очень-то дешевы, поэтому для случая работы с объектом в одного потоке были придуманы biased блокировки, которые не требуют никаких CAS операций. На самом требуется одна CAS операция для выставления типа блокировки при первой попытке её захвата.Таким образом для состязательных блокировок со многими потоками могут использоваться только fat блокировки, для не состязательных блокировок многими потоками выгоднее использовать thin локи и для работы с одним потоком выгоднее использовать biased блокировки.
Порядок выбора блокировок HotSpot
При работе JVM переход от одного вида блокировки к другому происходит в обратном описанному порядке. Т.е. стратегия на данный момент оптимистическая. При первом захвате блокировки, JVM надеется, что данная блокировка будет использоваться только одним потоком, и использует biased блокировку. Если же данную блокировку пытается захватить другой поток, то biased блокировка конвертируется в fat блокировку в случае состязательного захвата или в thin блокировку в случае не состязательного. Если блокировка была сконвертирована в thin и потом наступил состязательный захват из другого потока, то возможно он немного покрутиться в CAS и если не захватит блокировку через совсем короткий промежуток времени, то блокировка сконвертируется в fat, чтобы можно было пользоваться возможностью скедьюлинга потоков операционной системой.Применение данного сакрального знания
Казалось бы, какая разница программисту пишущему прикладной код от того, какую блокировку выберет JVM? Например, такой случай. В вашем приложении работа с каким-то объектом выполняется без состязания то одним, то другим потоком. Вы поставили синхронизацию и забыли об этом. Однако если вы знаете о типах блокировок, то вы, возможно, попытаетесь сделать так, чтобы работа с данным объектом практически всегда выполнялась одним и тем же потоком, тем самым JVM сможет задействовать biased блокировку вместо thin. Конечно же я не призываю на этом заморачиваться, но если во время реализации любой из подходов занимает одинаковые усилия, то почему бы сразу не сделать так, чтобы JVM могла использовать более эффективную блокировку.Второй случай, когда об этом полезно знать, был приведем на одном из докладов javaone. Проводился микро бэнчмарк сравнения производительности synchronized метода и метда с тем же содержимым, все тело которого заключено в synchronozed блок на this.
Сам тест был в виде метода main(), внутри которого сначала вызывался N-ое количество раз метод m1(), затем метод m2(). Результат - различие производительности в несколько раз. Объяснение же этому весьма простое - biased locking оптимизация включается не сразу, а после некоторой задержки, как стартовала JVM. Если тест запускать с параметром -XX:BiasedLockingStartupDelay=0, то время работы методов примерно совпадает.
У меня другой вопрос, который меня мучает уже несколько лет =)
ОтветитьУдалитьВот ты пишешь мьютексы системные в fat блокировках. А где джава столько сьютексов берет? Это же ограниченный ресур для ОС, их там десяток тысяч, сотня, но не сильно больше ...
Чувах рассказывал про какую-то таблицу мьютексов, что создает еще дополнительные расходы. Может она из себя некий пул представляет...
ОтветитьУдалитьА я тебя обманул, похоже: http://linux.die.net/man/3/pthread_mutex_init нет лимита. Странно, я был уверен, что есть.
ОтветитьУдалить>А где джава столько сьютексов берет?
ОтветитьУдалитьА сколько -- "столько"? В обычных джава-приложениях не особо много блокировок используется. Плюс разные оптимизации JVM - устранение блокировок в результате esacape analyze, внутри-JVM-ные блокировки, вроде описанных выше -- реальных-то fat-блокировок, конвертируемых в мьютексы -- мало.
ну вот положим, у нас много объектов, с методами, которые помечены synchronized - если нет biased & thin locking, то, как я понимаю, для каждого из объектов нужно у ОС попросить свой мьютекс
ОтветитьУдалитьВ реальности синхронизация на объектах -- явление не частое, поэтому мьютексов создаётся мало.
ОтветитьУдалитьне совсем понятно как делается Biased блокировка, если там даже CAS не используется в обычном режиме, как тогда определяется вмешательство другого потока? на пальцах еси можно )
ОтветитьУдалитьНа самлм деле одна-то CAS инструкция используется, как я нарисал в посте, а именно, используется она чтобы в самом начале когда первый поток пытается захватить монитор в заголовок обьекта монитора записать ID потока. Именно здесь единственный CAS гарантирует, что запишет туда свой ID только один поток. Второй поток увидит, что в заголовке монитора уже пропмсан ID другого потока и блокировка будет преобразована в более сильную.
ОтветитьУдалитьЕще мне тут умные люди подсказывают, что это будет работать только при условии, что у обьекта монитора не вызывается нативный хешкод, так как он сохраняется в то же место, что и ID, таким образом, в данном случае будет исползоваться толькоблокировка на мьютексах.
т.е. нативный хешкод хранится в заголовке объекта? и интересно, зачем его перетирает id потока, писать чтоль больше некуда...
ОтветитьУдалитьГде бы в спеках почитать это не в курсе?
а вот, у Руслана это есть: http://cheremin.blogspot.com/2011/04/how-caching-affects-hashing.html
ОтветитьУдалитьИлья, ты не на тот пост Руслана ссылку даешь.
ОтветитьУдалитьКонечная ссылка - это блог сана http://blogs.sun.com/dave/entry/biased_locking_in_hotspot. Я правда сам об этом не читал. Мне рассказал Илья, прочитав об этом на блоге Руслана (http://cheremin.blogspot.com/2011_04_13_archive.html). Вот такая вот цепочка :)
>т.е. нативный хешкод хранится в заголовке объекта? и интересно, зачем его перетирает id потока, писать чтоль больше некуда...
ОтветитьУдалитьДля скорости, очевидно. Ведь если с объектом хоть что-то делается, то его заголовок гарантированно горячий в кэше, значит доступ будет очень быстр.
А поскольку редко так бывает, чтобы у объекта использовался _и_ identity hash code, _и_ этот объект выступал монитором синхронизации -- есть смысл использовать это же место и для хранения признака привязки.
Решил проверить и получил почти одинаковые результаты для вызовов m1 и m2.
ОтветитьУдалитьВот код:
http://snipt.org/uJp4
Андрей, простите, что Вы пытались проверить?
ОтветитьУдалитьИдея данного примера была в том, что запуск бенчмарка двух методов m1 и m2 нельзя запускать последовательно. Вы же в своем примере, на сколько я понял, запускаете эти бенчмарки отдельно.
Я тут на досуге почитал http://www.oracle.com/technetwork/java/biasedlocking-oopsla2006-wp-149958.pdf. После чего хочу сделать уточнение: если вы посчитали identity hash code у объекта, то перестает работать только biased оптимизация, thin блокировки все еще работают, так как они хедер объекта копируют в объект лока.
ОтветитьУдалитьЕще там немного другая терминология thin лок называется lightweight, а fat лок - inflated.