Я помню, когда я впервые начал изучать Java, синхронизированный столкнулся, когда я столкнулся с многопоточной ситуацией. По сравнению с нами в то время синхронизированный был настолько волшебным и мощным. В то время мы дали ему имя «синхронизация», которое также стало хорошим лекарством для нас, чтобы решить многопоточные ситуации. Но, как мы узнаем, мы знаем, что синхронизированный - тяжелый замок, и по сравнению с замком, он будет казаться настолько громоздким, что мы думаем, что он не так эффективен и медленно отказался от него.
По общему признанию, с различными оптимизациями, сделанными Javas SE 1,6 на синхронизированном, синхронизированный не будет выглядеть таким тяжелым. Ниже следуйте LZ, чтобы исследовать механизм реализации синхронизации, как Java оптимизирует его, механизм оптимизации блокировки, структуру хранения блокировки и процесс обновления;
Принцип реализации
Синхронизированный может гарантировать, что метод или блок кодового блока, и только один метод может ввести в критическую область одновременно. В то же время это также может обеспечить видимость памяти общих переменных.
Каждый объект в Java может использоваться в качестве блокировки, что является основой для синхронизации для реализации синхронизации:
Общий метод синхронизации, блокировка является методом статической синхронизации текущего объекта экземпляра, блокировка является блоком метода синхронизации объекта класса, блокировка является объектом в скобках, когда поток обращается к блоку кода синхронизации, сначала необходимо получить блокировку для выполнения кода синхронизации. Когда исключение выходит или бросается, замок должен быть выпущен. Так как же он реализует этот механизм? Давайте сначала посмотрим на простой код:
Public Class SynchronizedTest {public Synchronized void test1 () {} public void test2 () {synchronized (this) {}}}Используйте инструмент Javap, чтобы просмотреть сгенерированную информацию файла класса для анализа реализации синхронизации
Как видно из вышеперечисленного, блок кода синхронизации реализован с использованием инструкций Monitorenter и Monitorexit. Метод синхронизации (он не показывает, что вам необходимо посмотреть на базовую реализацию JVM), опирается на реализацию ACC_SYNCHRONIZED в модификаторе метода.
Синхронный кодовый блок: инструкция мониторинга вставлена в начальную позицию блока кода синхронизации, а инструкция по мониторексуату вставлена в конечную позицию блока кода синхронизации. JVM должен гарантировать, что каждый монитор имеет мониторексит, соответствующий ему. У любого объекта есть монитор, связанный с ним, и когда монитор будет удерживаться, он будет заблокирован. Когда поток выполняет инструкцию мониторинга, он попытается получить владение монитором, соответствующее объекту, то есть попытаться получить блокировку объекта;
Синхронизированный метод: Синхронизированный метод будет переведен в обычные вызовы метода и инструкции по возврату, такие как: Invokevirtual и Areturn На уровне виртуальной коды нет особой инструкции для реализации синхронизированного модифицированного метода. Вместо этого, синхронизированное положение флага 1 в поле Method Access_flags метода помещается в таблицу методов файла класса, указывая, что метод представляет собой синхронизированный метод и использует объект, который называет метод или класс, принадлежащий методу для представления klass в виде блокировки (см.
Давайте продолжим анализировать его, но прежде чем мы пойдем глубже, нам нужно понять две важные понятия: заголовок объекта Java и монитор.
Заголовок объекта Java, монитор
Заголовки объектов Java и мониторы являются основой для реализации синхронизации! Ниже приведено подробное введение в эти два понятия.
Заголовок объекта Java
Замок, используемый для синхронизации, расположен в заголовке объекта Java, так что же является заголовком объекта Java? Заголовок объекта виртуальной машины горячей точки в основном включает в себя две части данных: Mark Word (Mark Field) и Klass Pointer (Type Pointer). Klass Point - это указатель на метаданные класса объекта. Виртуальная машина использует этот указатель, чтобы определить, какой экземпляр класса является объект. Mark Word используется для хранения собственных данных выполнения объекта. Это ключ к реализации легких замков и смещенных замков, поэтому на нем сосредоточено следующее.
Марк Слово.
Mark Word используется для хранения данных времени выполнения самого объекта, таких как хэш-код (хэшкод), возраст генерации GC, флаг состояния блокировки, блокировки, удерживаемые потоками, идентификатор нити, смещенной временной метки и т. Д. Виртуальная машина JVM может определить размер объекта Java через информацию о метаданных объекта Java, но не может подтвердить размер массива от метаданных массива, поэтому часть используется для записи длины массива. Следующим рисунком является структура хранения заголовка объекта Java (32-битная виртуальная машина):
Информация о заголовке объекта - это дополнительная стоимость хранения, которая не зависит от данных, определенных самим объектом. Однако, учитывая пространственную эффективность виртуальной машины, Mark Word разработана как не фиксированная структура данных для хранения как можно большего количества данных в очень небольшой пространственной памяти. Он будет повторно использовать свое собственное пространство для хранения в соответствии с состоянием объекта. То есть Марк Уорд изменится при работе программы, а состояние изменения выглядит следующим образом (32-битная виртуальная машина):
Краткое введение в заголовки объектов Java, давайте посмотрим на монитор.
Монитор
Что такое монитор? Мы можем понять его как инструмент синхронизации или механизм синхронизации, который обычно описывается как объект.
Так же, как все является объектом, все объекты Java являются естественными мониторами, и каждый объект Java может стать монитором, потому что в дизайне Java каждый объект Java несет невидимый замок, так как он выходит из матки. Это называется внутренней блокировкой или блокировкой монитора.
Монитор - это структура данных в частной собственности, принадлежащей потокам. В каждом потоке есть список доступных записей монитора и глобальный доступный список. Каждый заблокированный объект будет связан с монитором (Lockword в Markword заголовка объекта, указывая на адрес начала монитора). В то же время в мониторе есть поле владельца, в котором хранится уникальный идентификатор потока, который владеет блокировкой, указывая на то, что блокировка занята потоком. Его структура заключается в следующем:
Владелец: NULL в начале означает, что ни одна поток в настоящее время не владеет монитореропидом. Когда поток успешно владеет блокировкой, он сохраняет уникальную идентичность потока, и когда блокировка выпускается, он устанавливается на NULL;
Intryq: связать систему Mutex (Semaphore), чтобы заблокировать все потоки, которые пытаются заблокировать мониторректор.
Rcthis: представляет количество всех потоков, заблокированных или ожидающих монитореропическогор.
Гнездо: используется для реализации подсчета блокировки повторного входа.
HashCode: сохраняет значение HashCode, копируемое из заголовка объекта (также может содержать GCAGE).
Кандидат: используется, чтобы избежать ненужного блокировки или ожидания, когда просыпаются потоки, потому что только один поток может успешно владеть блокировкой каждый раз. Если предыдущий поток, который высвобождает блокировку, каждый раз разбудит все блокирующие или ожидающие резьбы, это приведет к ненужному переключению контекста (от блокировки до готовности, а затем снова блокируется из -за сбоя блокировки конкуренции) и, таким образом, приведет к серьезному деградации производительности. Кандидат имеет только два возможных значения: 0 означает, что нет нити, который необходимо разбудить до 1 средств, чтобы разбудить поток преемника, чтобы конкурировать за блокировку.
Ссылка: поговорите о синхронизированном в параллелизме Java
Мы знаем, что синхронизированный - тяжелый замок, и его эффективность не очень хороша. В то же время эта концепция всегда была в наших умах. Тем не менее, реализация синхронизации была оптимизирована в JDK1.6, что делает его не таким тяжелым. Итак, какие методы оптимизации используют JVM?
Оптимизация блокировки
JDK1.6 вводит множество оптимизаций в реализацию замков, таких как пряди, адаптивные спиновые замки, устранение блокировки, скорлупа блокировки, блокировки смещения, легкие замки и другие технологии, чтобы уменьшить накладные расходы на работу блокировки.
Существует четыре основных состояния замков, а именно: состояние без блокировки, предвзятое состояние замка, легкое состояние замка и состояние в тяжелом весе. Они будут постепенно обновляться с жесткой конкуренцией. Обратите внимание, что блокировки могут быть обновлены и не могут быть понижены. Эта стратегия заключается в повышении эффективности получения и выпуска замков.
Спин блокировка
Блокировка и пробуждение потока требуют, чтобы процессор переходил от состояния пользователя на основное состояние. Частое блокирование и пробуждение являются тяжелой задачей для процессора, и он неизбежно окажет сильное давление на одновременную производительность системы. В то же время мы обнаружили, что во многих приложениях состояние блокировки блокировки объекта будет длиться только в течение очень короткого периода времени. Очень недостойно часто блокировать и разбудить поток в течение этого очень короткого периода времени. Таким образом, вводится спин -блокировка.
Что такое спин -блокировка?
Так называемая спиновая блокировка состоит в том, чтобы позволить потоке подождать некоторое время и не быть немедленно подвешено, чтобы увидеть, будет ли резьба, удерживающая блокировку быстро, быстро выпустит блокировку. Как подождать? Просто выполните бессмысленный цикл (спин).
Ожидание спина не может заменить блокировку, давайте поговорим о требованиях по количеству процессоров (многоядерный, кажется, что сейчас нет единого процессора). Хотя это может избежать накладных расходов, вызванных переключением потоков, он занимает время процессора. Если нить, удерживающая блокировку, быстро выпустит блокировку, эффективность вращения будет очень хорошей. Напротив, спиновая нить будет напрасной поглощать ресурсы обработки. Это не будет делать никакой значимой работы. Обычно он занимает яму и не дерьмо, что вместо этого приведет к отходам производительности. Следовательно, время для ожидания вращения (количество спинов) должно иметь предел. Если спин превышает определенное время и до сих пор не получило блокировку, его следует приостановить.
Spinlock представлен в JDK1.4.2 и выключен по умолчанию, но его можно включить с -xx:+USESPINNing, и включен по умолчанию в JDK1.6. Количество спинов по умолчанию одновременно составляет 10 раз, что может быть скорректировано с помощью параметра -xx: preblockspin;
Если вы отрегулируете количество спиновых спинов спиновой блокировки через параметр -xx: preblockspin, это принесет много неудобств. Если я настраиваю параметр до 10, но многие потоки в системе отпустите блокировку, когда вы просто выйдете (если вы вращаетесь один или два раза, вы можете получить блокировку), вы бы смущены? Таким образом, JDK1.6 представил адаптивные спиновые замки, делая виртуальные машины умнее и умнее.
Адаптировать спин блокировку
JDK1.6 представляет более умный спин -блокировка, а именно адаптивный спин. Так называемая адаптация означает, что количество спинов больше не исправлено, оно определяется временем вращения на том же замке и состоянии владельца блокировки. Как это сделать? Если поток успешно вращается, количество спинов будет более в следующий раз, потому что виртуальная машина считает, что с тех пор, как в последний раз он был успешным, спин, вероятно, снова будет успешным, и это позволит спину ждать больше раз. Напротив, если немногие спины могут быть успешными для определенного блокировки, количество спинов будет уменьшено или даже опущено, когда замок потребуется в будущем, чтобы не тратить ресурсы процессора.
Благодаря адаптивным спиновым замкам, поскольку информация о работе программы и мониторинга производительности продолжает улучшаться, прогноз виртуальной машины статуса блокировки программы станет все более и более точным, а виртуальные машины станут умнее.
Заблокировать устранение
Чтобы обеспечить целостность данных, нам необходимо синхронизировать эту часть операции при выполнении операций, но в некоторых случаях JVM обнаруживает, что нет никакой возможности конкуренции по общим данным, поэтому JVM будет блокировать эти блокировки синхронизации. Основой для устранения блокировки является поддержка данных для анализа побега.
Если соревнования нет, почему вам все еще нужно добавлять замок? Таким образом, устранение блокировки может сэкономить время, бессмысленно запрашивая блокировки. Независимо от того, необходимо ли для виртуальных машин переменная уход, чтобы определить, используется ли анализ потока данных, но неясно ли для нас программистов? Будем ли мы добавить синхронизацию перед блоком кода, который четко знает, что нет конкуренции данных? Но иногда программа не то, что мы думаем? Хотя мы не отображаем блокировку, когда мы используем некоторые встроенные API JDK, такие как Stringbuffer, Vector, Hashtable и т. Д., В настоящее время будет невидимая операция блокировки. Например, метод stringbuffer's Append () и метод add () add ():
public void vectortest () {vector <string> vector = new Vector <string> (); for (int i = 0; i <10; i ++) {vector.add (i+""); } System.out.println (Vector); }При запуске этого кода JVM может четко обнаружить, что вектор переменной не выходит из метода vectortest (), поэтому JVM может смело устранить операцию блокировки внутри вектора.
Заблокировать шероховатое
Мы знаем, что при использовании блокировки синхронизации объем блока синхронизации должен быть максимально небольшим - только синхронизируется в фактическом объеме общих данных. Цель этого состоит в том, чтобы минимизировать количество операций, которые должны быть синхронизированы как можно больше. Если соревнование блокировки, поток, ожидающий блокировки, также может получить замок как можно скорее.
В большинстве случаев вышеупомянутое мнение верно, и LZ всегда придерживался этой точки зрения. Однако, если серия непрерывных операций блокировки и разблокировки может привести к ненужным потерям производительности, вводится концепция блокировки Slutty.
Концепция клеветника легче понять, чтобы соединить несколько операций непрерывной блокировки и разблокировки и расширения в блокировку с большим диапазоном. Как показано в приведенном выше примере: вектор должен добавлять операцию блокировки каждый раз, когда он добавляет. JVM обнаруживает, что один и тот же объект (вектор) непрерывно заблокирован и разблокирован, и будет объединять больший диапазон заблокированных и разблокированных операций, то есть заблокированная и разблокированная операция будет перемещена за пределы петли.
Легкий замок
Основная цель внедрения легких замков состоит в том, чтобы уменьшить потребление производительности, вызванное использованием Mutexes операционной системы под предпосылкой нескольких соревнований без нескольких потоков. Когда функция блокировки смещения отключена или несколько потоков конкурируют за блокировки смещения, блокировка смещения будет обновлен до легкого блокировки, затем будет предпринята попытка легкого блокировки, а шаги следующие:
Получите замок
Определите, находится ли текущий объект в состоянии без блокировки (Hashcode, 0, 01). Если это так, JVM сначала создаст пространство, называемое записи блокировки в кадре стека текущего потока, чтобы сохранить текущую копию слова объекта блокировки (чиновник добавляет перемещенную префикс в эту копию, а именно перемещенную марку); В противном случае, выполнить шаг (3);
JVM использует операцию CAS, чтобы попытаться обновить черту объекта до исправления, указывающего на записи блокировки. Если он успешно указывает на то, что блокировка оспаривается, флаг блокировки будет изменен на 00 (указывая на то, что объект находится в легком состоянии блокировки) и выполняет операцию синхронизации; Если он не удается, выполните шаг (3);
Определите, указывает ли отметка слова текущего объекта на кадр стека текущего потока. Если это так, это означает, что текущий поток уже удерживал блокировку текущего объекта, а затем непосредственно выполняет блок синхронного кода; В противном случае это может означать только то, что объект блокировки был захвачен другими потоками. В настоящее время легкий замок должен быть расширен в тяжелую блокировку, бит флага блокировки становится 10, а ожидающая нить позже войдет в состояние блокировки;
Отпустите замок
Выпуск легких замков также осуществляется через операции CAS, а основные шаги следующие:
Удалить данные, сохраненные в перемещенном значении, в легком блоке сбора;
Замените полученные данные в Mark Word на работу CAS. Если это успешно, это означает, что блокировка успешно выпускается, иначе она будет выполнена (3);
Если замена операции CAS не удается, это означает, что другие потоки пытаются приобрести блокировку, и подвесная потока необходимо пробудить при выпуске блокировки.
Для легких замков основа для повышения производительности заключается в том, что «Для большинства замков не будет конкуренции на протяжении всего жизненного цикла». Если эта основа нарушена, в дополнение к взаимоисключающим накладным расходам, есть дополнительные операции CAS. Следовательно, в случае многопоточной конкуренции легкие замки медленнее, чем в тяжелом весе;
На следующем рисунке показан процесс сбора и выпуска легких замков
Положительный замок
Основная цель внедрения предвзятых замков-минимизировать ненужные пути легкого выполнения блокировки без многопоточной конкуренции. Вышеупомянутое, что операция блокировки и разблокировки легких замков требует полагаться на несколько атомных инструкций CAS. Так как же смещение блокировки уменьшает ненужные операции CAS? Мы можем увидеть структуру марки. Вам нужно только проверить, является ли это смещенной блокировкой, блокировка идентифицируется как и Threadid. Поток обработки выглядит следующим образом:
Получите замок
Обнаруйте, находится ли Mark Word в смещном состоянии, то есть, является ли это смещенным замком 1, а бит идентификации блокировки составляет 01;
Если это смещаемое состояние, проверьте, является ли идентификатор потока текущим идентификатором потока. Если да, выполните шаг (5), в противном случае выполните шаг (3);
Если идентификатор потока не является идентификатором текущего потока, то блокировка конкурируется за операцию CAS. Если конкуренция успешна, идентификатор потока Mark Word заменяется текущим идентификатором потока, в противном случае поток (4);
Замок конкурса CAS не удается, что доказывает, что в настоящее время существует многопоточная конкурентная ситуация. Когда глобальная точка безопасности достигается, поток, который получает смещенную блокировку, приостановлен, смещенная блокировка обновляется до легкой блокировки, а затем поток, заблокированный в точке безопасности, продолжает выполнять блок синхронного кода;
Выполнить синхронные кодовые блоки
Выпустить замки. Выпуск предвзятого замка принимает механизм, в котором только конкуренция может выпустить замк. Поток не будет активно выпускать предвзятую блокировку и потребуется ждать конкурирования других потоков. Отставление предвзятых замков требует ожидания глобальной точки безопасности (этот пункт заключается в том, что не существует кода, выполняемого в). Шаги следующие:
Приостановите резьбу с смещенным замком и определите, находится ли камень объекта Lock еще в заблокированном состоянии;
Разблокировать смещение в сторону SU и восстановить в состоянии без блокировки (01) или легкое состояние блокировки;
На следующем рисунке показан процесс сбора и выпуска предвзятых замков
Тяжелый замок
Замок веса также называется монитором объекта (монитор) в JVM. Он очень похож на Mutex в C. В дополнение к тому, чтобы иметь взаимоисключающую функцию Mutex (0 | 1), она также отвечает за реализацию функции семафора, то есть он содержит, по крайней мере, очередь замков соревнований и очередь блокировки сигнала (очередь подождите). Первый несет ответственность за взаимное исключение, а последний используется для синхронизации потоков.
Служные замки реализуются через мониторы (мониторы) внутри объектов, где сущность мониторов заключается в том, чтобы полагаться на реализацию Mutex Lock в базовой операционной системе. Переключение между потоками операционной системы требует переключения с состояния пользователя на состояние ядра, а стоимость переключения очень высока.
Суммировать
Выше приведено все содержание этой статьи о подробном объяснении принципа реализации синхронизации в Java. Я надеюсь, что это будет полезно для всех. Если есть какие -либо недостатки, пожалуйста, оставьте сообщение, чтобы указать это. Спасибо, друзья, за вашу поддержку на этом сайте!