Не паникуйте, когда столкнетесь | Эта статья перенесет вас в другую синхронизированную

интервью Java
Не паникуйте, когда столкнетесь | Эта статья перенесет вас в другую синхронизированную

«Эта статья участвовала в мероприятии Haowen Convocation Order, щелкните, чтобы просмотреть:Двойные заявки на внутреннюю и внешнюю стороны, призовой фонд в 20 000 юаней ждет вас, чтобы бросить вызов!"

В предыдущей статье мы потратили много времени на анализ и пониманиеvolatileКлючевые слова, заинтересованные студенты могут выйти и повернуть налево«Здравствуйте, расскажите, пожалуйста, о ключевом слове volatile? (полная статья)". законченныйvolatile, конечно, без разговоровsynchronized, в параллельном программировании это ключевое слово всегда существовало на старшем уровне, мы привыкли называть еготяжелый замок, но сJava SE 1.6После версии,Javaкомандная параsynchronizedОн был тщательно оптимизирован, так что он может быть легким или тяжелым, соленым или сладким (ерунда). Следуйте моему видению, и давайте посмотрим, что это такое!

1. Душевная пытка

Прежде чем перейти к сути, это все еще клишированная пытка души, вы готовы?

  • чтоsynchronized, как он используется?
  • synchronizedДва способа реализовать блокировку объекта и как это работает?
  • говорить оsynchronizedЗаблокировать процесс эскалации?
  • synchronizedиvolatileразница?

Я не знаю, сложно ли это для вас, позвольте мне провести вас, чтобы проанализировать эту технологию от более мелкого к более глубокому.

2. Базовое приложение

2.1 Метод блокировки

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

  • Статический метод действует на текущий объект класса, то есть гранулярность блокировки - это сам объект класса.Для входа в статический метод необходимо получить блокировку объекта класса.
  • Метод экземпляра действует на экземпляр текущего класса, то есть гранулярность блокировки — это экземпляр класса, и для входа в метод экземпляра необходимо получить блокировку экземпляра класса.
  • Блок кода воздействует на указанный объект блокировки, то есть гранулярность блокировки представляет собой указанный объект блокировки, и для входа в блок кода необходимо получить блокировку указанного объекта.

2.2 Применение кода

public class SyncExample {
    
    private static final Object LOCK = new Object();

    private static int i = 1;

    public synchronized static void increase1(){
        i++;
    }

    public synchronized void increase2(){
        i++;
    }

    public void increase3(){
        synchronized (LOCK){
            i++;
        }
    }
}

#increase1Метод представляет собой модифицированный статический метод;#increase2Метод представляет модифицированный метод экземпляра;#increase1Метод представляет собой оформленный блок кода.

3. Блокировка схемы хранения

synchronizedВсегда связан с объектом. Если метод статический, ассоциированным объектом является класс; если метод нестатический, ассоциированным объектом является экземпляр. Если это кодовый блок, то указанный объект. Очевидно, блокировка записывается в объект. Итак, возникает вопрос,synchronizedК чему относится замок? В простом понимании замок — это общий ресурс, который записывает, кто им владеет, каково текущее состояние и т. д. Давайте сначала проанализируем, как объекты хранятся в памяти.

существуетHotspot JVMсередина,Java ObjectСхема хранения объектов в памяти разделена на три области, а именно: заголовок объекта, демонстрационные данные и заполнение объекта. Как показано на рисунке, схема хранения массивов и объектов очень похожа, за исключением того, что заголовок объекта больше, чем длина массива, потому что массив должен хранить свою собственную длину, которая составляет 4 байта.

Java Object.png

Как видно из рисунка, заголовок объекта включает в себя две части, а именно тег объекта и метаинформацию класса (указатель типа). тег объекта, т.е.Markwordобъект храненияhashCode, GCИнформация и замки. Метаинформация класса хранит «указатели на информацию об объекте класса». в 32 битJVM , заголовок объекта занимает 8 байт, а 64-битныйJVM Занимает 16 байт.

markword.png

Как показано выше, этоMarkwordкласс в 32-битнойJVM план хранения для различных ситуаций,MarkwordХранящиеся в нем данные будут изменяться при изменении бита флага блокировки, и примерно сохраненные изменения делятся на пять ситуаций. На рисунке мы можем увидеть процесс перехода от блокировки без блокировки -> смещенная блокировка -> облегченная блокировка -> хранилище тяжеловесных блокировок, что представляет собой процесс укрупнения блокировок.

Итак, вопрос в том, могут ли все объекты реализовывать блокировки? Ответ положительный.

  • Сначала у нас есть дляJavaСуществует общее понимание того, что все объекты являются производными отObject, каждый объект хранится в памяти, как показано на нашем рисунке, есть заголовок объекта, а заголовок объекта имеетMarkwordТег объекта. Важно отметить, что хранилище объектов включает в себяMarkwordРеализация маркировки объектовnativeДа, обаC++Объекты, реализованные языком.
  • Когда поток получает блокировку, он фактически получает объект монитора, который является объектом синхронизации.Java Objectсодержит этот объект. Аналогично, этот объект такжеnativeиз.

В-четвертых, обновление блокировки

Java 1.6До,synchronizedЭто стандартная тяжеловесная блокировка. Когда несколько потоков конкурируют за общие ресурсы, потоки, которые не конкурировали за ресурсы, всегда будут заблокированы, а производительность высока. В то же время для тяжеловесных блокировок блокировка и освобождение блокировок также потребляют много ресурсов. Чтобы уменьшить накладные расходы на производительность и повысить эффективность, люди разделили четыре состояния блокировки для различных сценариев блокировки, включая отсутствие блокировки, предвзятую блокировку, облегченную блокировку и усиленную блокировку.Степень непрерывно повышается от низкого до высокого.

4.1 Блокировка смещения

Во многих случаях блокировки всегда запрашиваются одним и тем же потоком несколько раз, и потоки, конкурирующие за блокировки, отсутствуют. Для такой ситуации очень подходит смещенный замок, так когда же смещенный замок? В главе 3 мы перечисляемsynchronizedВ разных состояниях блокировки,MarkwordСуществует большая разница в структуре памяти.

4.1.1 Получение предвзятой блокировки

image.png

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

4.1.2 Аннулирование блокировки смещения

Отзыв предвзятой блокировки на самом деле не отменяет блокировку и не переводит ее в свободное от блокировки состояние. Для отзыва смещенных замков есть два случая для исходной удерживающей нити и самого замка.

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

На рисунке ниже показаноsynchronizedВ модифицированном блоке кода синхронизации процесс потока T1 и потока T2 последовательно конкурирует за ресурсы блокировки.

未命名文件.png

4.2 Легкий замок

В предыдущем разделе упоминалось, что два потока конкурируют за блокировки, что приводит к отзыву предвзятых блокировок.В процессе отзыва происходит обычное расширение блокировки, то есть обновление до облегченных блокировок. Облегченные блокировки подходят для сценариев, в которых два потока конкурируют за ресурсы блокировки, а синхронизированные блоки кода выполняются быстро. что в объектеMarkwordЧто изменилось в схеме хранения?

4.2.1 Облегченный процесс обновления

Как мы все знаем, вJVM, стек является частным для потока. Первый шаг в обновлении до легковесного замка — сделать что-то в кадре стека.

  1. Кадр стека вновь созданной записи блокировкиLockRecord, запись включаетdisplaced hdrиowner.
  2. заблокирует объект в заголовкеMarkwordСодержимое копируется в только что созданный кадр стека.LockRecord.
  3. записать замокLockRecordгде владелец указывает на объект блокировки.
  4. Наконец, заголовок объектаMarkwordУказатель на запись блокировки в стеке указывает на запись блокировкиLockRecord(Этот шаг являетсяMarkwordсохранить реальное изменение содержания).

Процесс изменения показан на рисунке ниже.

未命名文件.png

4.2.2 Облегченный соревновательный процесс

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

Так почему же поток, не завладевший ресурсом блокировки, ожидает циклически, а не блокируется?Наиболее важной причиной этого является необходимость блокировки и пробуждения потока.CPUОт пользовательского режима до режима ядра частые блокировки и пробуждения являются тяжелым бременем для ЦП, что неизбежно оказывает большое влияние на одновременную производительность операционной системы. Так что возьмите петлю, чтобы подождать, этоблокировка спина, СюдаAQSТакже используется нижний слой замка.

Так как же поток, который не получил ресурс блокировки, ожидает циклически?Постоянный цикл потребляетCPUПроизводительность, конечно же, у данной спиновой блокировки есть условие остановки, которое делится на два случая.

  • существуетJava 1.6Ранее было установлено количество вращений, и цикл будет прекращен, если количество циклов будет превышено.Как правило, количество раз установлено 10, что можно установить, установивHotSpot 参数 -XX:PreBlockSpinЧтобы изменить, перед изменением этого параметра вам необходимо сначала установить параметр-XX:+UseSpiningВключите блокировку вращения.
  • существуетJava 1.6Позже, по сравнению с интеллектуальной адаптивной блокировкой вращения, этот метод определяет время самовыбора блокировки в соответствии с предыдущим временем и состоянием самовыбора той же блокировки, а не фиксированным количеством вращений.
4.2.3 Снятие блокировки Spinlock

Когда поток, который не получил ресурс блокировки, не может получить блокировку путем вращения, блокировка будет обновлена ​​до тяжеловесной блокировки, а заголовок объекта блокировки будет изменен.MarkwordЗначение в измененном содержимом примерно соответствует указателю на тяжеловесную блокировку, а флаг модифицированной блокировки равен 10. В этот момент поток блокируется.

Когда синхронизированный блок кода, которому принадлежит блокировка, выходит, он передается черезCASСтек хранимых записейMarkwordсодержимое и текущий объект блокировкиMarkwordсравнить затем установить, потому что текущийMarkwordКонтент изменился, и ему точно не удастся установить значение.В это время поток снимет блокировку, освободит монитор (монитор) и разбудит ожидающий поток. Затем пробуждается другой заблокированный поток, чтобы повторно конкурировать за ресурс блокировки.

偏向锁升级流程图 (1).png

4.3 Тяжелый замок

В предыдущем разделе упоминалось, что два потока конкурируют за ресурсы блокировки. Поток, который не получил ресурс блокировки, не получает ресурс блокировки в рамках политики спина, и облегченная блокировка будет обновлена ​​до тяжелой блокировки. реальная блокировка., это мьютекс, блокирующие и разблокирующие ресурсы очень ресурсоемкие. Так где же этот замок и как он выглядит?

4.3.1 Где находится замок

Когда блокировка модернизируется до тяжеловесной блокировки, наиболее очевидным изменением является объект блокировки.MarkwordТокен блокировки становится10, указанное содержимое становится указателем на объект монитораMonitor. Как этот объект монитора реализует блокировку мьютекса? Давайте напишем кусок кода, чтобы проверить это.

public class SyncExample {
    private static int i = 1;
    public synchronized static void increase1() {
        i++;
    }
    public static void main(String[] args) {
        synchronized (SyncExample.class){

        }
        SyncExample.increase1();
    }
}

Мы сначалаjavaфайл, скомпилированный вSyncExample.class, то черезjavap -v SyncExample.class Инструкция декомпилирует указанныйJavaфайл с байт-кодом.

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #3                  // class me/stone/training/platform/training/java/thread/SyncExample
         2: dup
         3: astore_1
         4: monitorenter
         5: aload_1
         6: monitorexit
         7: goto          15
        10: astore_2
        11: aload_1
        12: monitorexit
        13: aload_2
        14: athrow
        15: invokestatic  #4                  // Method increase1:()V
        18: return

Мы можем ясно видеть, что в блоке синхронизированного кода больше байт-кода.monitorenterиmonitorexit, эти две инструкции очень важны.

  • monitorenterПредставляет для получения объекта монитораmonitorПриобретение других конкурирующих потоков может войти только после успеха очереди ожидания, она заблокирована.
  • monitorexitПредставляет объект монитора выпускаmonitorправа собственности, чтобы другие заблокированные потоки могли попытаться получить объект монитора.
4.3.2 Объекты мониторинга

synchronizedМодифицированный блок синхронизации, когда несколько потоков конкурируют за ресурсы, конкуренция фактически является объектом блокировки, и этот объект блокировки являетсяObjectОбъект, который содержит объект в памяти, заголовок объекта имеет указатель наMarkword, когда замок модернизируется до усиленного замка,Markwordуказывает наMonitorобъект, этот объект на самом деле родной, вJVM Hotspot, этот объект по существу являетсяC++ объект, представляет собойObjectMonitorэкземпляр класса.

未命名文件.png

Как показано на фиг.MonitorЕсть четыре важных свойства.

  • _count,прилавок. Используется для подсчета количества попыток блокировки, мы часто говоримsynchronizedэто реентерабельная блокировка, которая является ключом к реализации.
  • _owner, запишите нить текущего держателя замка, то есть того, кто в данный момент держит замок.
  • _waitSet, очередь ожидания, как следует из названия, когда поток вызывает метод ожидания объекта, поток освобождает ресурсы и входит в очередь, чтобы дождаться пробуждения.
  • _EntrySet, очередь входа, в этой очереди ставятся в очередь потоки, которые не могут конкурировать за блокировки, и эти потоки находятся в состоянии блокировки.

Нить приобрела замок в соревновании тяжеловесных замков, в это время_ownerуказывает на текущий поток,_countСчетчик увеличивается на 1, блокировка не получена потоком вentryОчередь в очереди. Если поток, получивший ресурс блокировки, снова входит в блок синхронизации, управляемый тем же ресурсом блокировки, поскольку суждение_ownerуказывая на себя, поэтому_countДобавьте 1, чтобы реализовать блокировку с повторным входом. Наконец, выйдите из синхронизированных блоков по очереди, постепенно уменьшите счетчик на 1 и, наконец, уменьшите его до 0, выйдите из всех синхронизированных блоков, освободите ресурсы блокировки и уведомитеentryОчередь может упорядочивать потоки в очереди, чтобы они конкурировали за ресурсы блокировки.

waitОчередь очень особенная, поток выполняется в процессе удержания блокировки#waitметод, поток освободит объект блокировки и войдет в эту очередь. все жду#notifyМетод просыпается, а затем входит в очередь ожидания, чтобы конкурировать с другими потоками за ресурсы блокировки.

должны знать о том,#notifyМетод заключается в том, чтобы взять случайныйwaitПотоки в очереди помещаются вentryочередь,#notifyAllзаключается в том, чтобы поместить все ожидающие потоки вentryочередь .

4.3.3 Нечестная блокировка

synchronizedТяжеловесная блокировка не является справедливой блокировкой, то есть когда поток, удерживающий ресурс, освобождает ресурс блокировки, он помещается вentryПотоки очереди будут синхронизироваться и конкурировать за ресурсы блокировки и не позволят потоку с наибольшим временем ожидания напрямую получить блокировку, и даже возможно, что, когда новый поток внезапно попытается получить блокировку, поток, удерживающий ресурс блокировки, просто освобождает блокировку.Поток получит ресурс блокировки. Недостатком несправедливых блокировок является то, что некоторые потоки могут не иметь возможности получить ресурсы блокировки в течение длительного времени и находятся в состоянии голодания.Если вы хотите использовать справедливые блокировки, вы можете использоватьReentrantLockчестный режим блокировки.

Пять, сравните volatile

предыдущая статьяНе паникуйте при встрече | Здравствуйте, можете рассказать о ключевом слове volatile? (полная статья)подробныйvolatileИспользование ключевых слов, принцип. В этой статье мы обобщаемsynchronizedиvolatileглавное отличие.synchronizedиспользуется для реализации модели многопоточности на основе блокировки; иvolatileвключаютAtomicявляется неблокирующим, что означает, что потоки не нуждаются в блокировках для доступа к общим переменным.

synchronized volatile
работает только для блоков или методов просто работает с переменными
Он основан на резьбовой модели замков. С помощью аппаратного обеспечения, плюс неблокирующий алгоритм, это неблокирующий
Поскольку блокировка устанавливается и снимается, производительность относительно низкая по сравнению с энергозависимой. Относительно высокая производительность по сравнению с синхронизированным
Потому что он основан на замках, он может принести риски параллелизма, такие как тупики и живые Поскольку он не блокируется, он не подвержен рискам параллелизма, таким как взаимоблокировки, живые блокировки и т. д.

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

synchronized Он гарантирует, что часть кода не может быть доступна нескольким потокам одновременно, устраняет все возможные условия гонки, и, поскольку он основан на блокировках, он очень дорог в использовании и приносит в жертву преимущества многопоточного параллелизма некоторым из них. протяженность - скорость.

Итак, как использовать эти два ключевых слова? В дополнение к различным местам, где используется код, есть еще два принципа.

  • Когда переменная будет прочитана несколькими потоками, но записана только одним потоком, используйтеvolatile.
  • Используется, когда несколько потоков читают и записывают переменныеsynchronized.

6. Резюме

Это конец всей статьи. Можете ли вы ответить на вопросы, упомянутые в начале статьи? Я стараюсь писать максимально подробно, но должны быть какие-то очень низкоуровневые принципы, которые не были освещены.Надеюсь, читатели смогут оставить сообщение в комментариях и обсудить вместе. Следующая статья о многопоточностиAQSПринципы применения и реализации.


Брат, не паникуй! Не стесняйтесь оставлять лайки, обсуждать и комментировать. Добро пожаловать в колонку интервьюНе паникуйте, когда сталкиваетесь | Параллельное программирование на Java, Не беспокойтесь о повышении зарплаты во время собеседования. Также добро пожаловать, чтобы следовать за мной, я должен быть лучшим человеком.