Трагедия на интервью, вызванная String, String Builder, String Buffer

Java

предисловие

pexels-pixabay-60504

Разница Строка, StringBuilder, StringBuffer - это что? Эта оценка каждая во всех вопросах Java Face должна встречаться. Когда смутно помните первое собеседование, когда интервьюер задал мне этот вопрос, думая так, какая разница делает все вещью строки. После глубокого понимания этой проблемы и обнаружил, что не просто?

Закуска

интервьюер: Привет, ты другой технарь, не так ли?

домик: Привет, интервьюер, я другой технарь.

интервьюер: Здравствуйте, пожалуйста, сделайте краткое представление о себе.

домик: Меня зовут другой технический дом, я родом из ххх, и проекты, которые я сделал, в основном включают хххх с использованием технологий ххх, ххх.

20180719930824_lrEKIh

интервьюер: Хорошо, у меня есть общее представление о вашем резюме, так что давайте сначала поговорим об основах.

домик: ОС сердца

Закуска

интервьюер: String, StringBuilder, StringBuffer разница?

домик: Это слишком просто, это смотрит на меня свысока?

  • С точки зрения изменчивости String неизменяем, а длина StringBuilder и StringBuffer является переменной.
  • С точки зрения скорости работы, StringBuilder > StringBuffer > String.
  • С точки зрения безопасности потоков StringBuilder небезопасен для потоков, а StringBuffer — потокобезопасен.

  So String: подходит для небольшого количества строковых операций, StringBuilder: подходит для большого количества операций в буфере символов в рамках одного потока, StringBuffer: подходит для большого количества операций в буфере символов в условиях многопоточности.

75f3cd331b7ab4ca4552847a746952da

интервьюер: Почему String неизменяем?

домик: Поскольку массив символов, в котором хранятся данные, украшен финалом, он неизменяем.

image-20200714151350294

интервьюер: Я только что сказал, что String неизменяем, но после выполнения следующего кода он изменился, почему так?

public class Demo {

    public static void main(String[] args) {
        String str = "不一样的";
        str = str + "科技宅";
        System.out.println(str);
    }

}

Очевидно, что результатом выполнения вышеизложенного является:Технология - это не тот дом.

мы сначала используемjavac Demo.classСкомпилировать, затем декомпилироватьjavap -verbose DemoПолучил следующий результат:

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: ldc           #2                  // String 不一样的
         2: astore_1
         3: new           #3                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
        10: aload_1
        11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        14: ldc           #6                  // String 科技宅
        16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        22: astore_1
        23: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        26: aload_1
        27: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        30: return

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

public class Demo {

    public static void main(String[] args) {
        String str = "不一样的";
        StringBuilder builder =new StringBuilder();
        builder.append(str);
        builder.append("科技宅");
        str = builder.toString();
        System.out.println(str);
    }

}

мы можем посмотреть наbuilder.toString();реализация.

@Override
public String toString() {
  // Create a copy, don't share the array
  return new String(value, 0, count);
}

Это понятноtoStringметод заключается в создании новогоStringобъект вместо изменения старогоstrсодержание, эквивалентное размещению старогоstrСсылки на новыеStringобъект. ЭтоstrПричина изменения.

Поделитесь вопросом интервью, с которым я столкнулся, и вы можете догадаться, каков ответ?В конце статьи есть анализ

public class Demo {

    public static void main(String[] args) {
        String str = null;
        str = str + "";
        System.out.println(str);
    }

}

интервьюер: Можно ли наследовать класс String?

домик: Нет, поскольку класс String изменяется с помощью ключевого слова final, он не может быть унаследован, а StringBuilder и StringBuffer также изменяются с помощью ключевого слова final.

интервьюер: Почему String Buffer является потокобезопасным?

домик: Это потому, что вStringBufferВ классе используются обычно используемые методыsynchronizedОднако синхронизированный и, следовательно, потокобезопасныйStringBuilderнисколько.这也就是运行速度StringBuilder > StringBufferпричина.

20181119593911_rYDslC

Интервьюер: Только что вы сказалиsynchronizedКлючевое слово, о котором можно говоритьsynchronizedформа выражения?

домик:

  • Для обычных синхронизированных методов блокировкой является текущий объект экземпляра.
  • Для статических синхронизированных методов блокировкой является объект класса текущего класса.
  • Для синхронизированных блоков метода блокировка является объектом конфигурации синхронизированной скобки.

интервьюер: можно говоритьsynchronizedПринципиальная вещь?

домик:synchronizedэто тяжеловесная блокировка, реализация зависит отJVMизmonitorБлокировка монитора. В основном используетсяmonitorenterиmonitorexitДирективы для реализации синхронизации методов и синхронизации блоков кода. При компиляции будетmonitorexitИнструкция в начальной позиции блока кода синхронизации, иmonitorexitВставьте окончания методов и исключения, и каждыйmonitorexitимеет соответствующийmonitorexit.

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

приходят твердые овощи

интервьюер: Вы упомянули ранееsynchronizedЭто тяжелый замок, вы знаете, как его оптимизировать?

006qir4oly1g0qzhmov3tj30gw0cgjte

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

Во-первых, это состояние без блокировки.Когда поток входит в блок кода синхронизации, он проверяет, сохранен ли идентификатор текущего потока в записи блокировки в заголовке объекта и во фрейме стека. не используетсяCASсделать замену. Потоку не нужно входить и выходить из блока синхронизированного кода позже.CASоперация блокировки и разблокировки, нужно только судить заголовок объектаMark wordСохранять ли смещенную блокировку, указывающую на текущий поток. Если yes указывает, что блокировка была получена, если нет или нет, вам нужно использоватьCASЗамена, если настройка выполнена успешно, текущий поток удерживает блокировку смещения, в противном случае блокировка смещения будет отозвана и преобразована в упрощенную блокировку.

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

   Облегченный процесс разблокировки замка, при разблокировке CAS будет использоваться дляDisplaced Mark WordЗамените его обратно в заголовок объекта. Если это успешно, это означает, что конкуренция не произошла. В противном случае это означает, что нынешний замок расширит в тяжелый замок, если есть конкурирующая блокировка.

Схема процесса обновления

533411-20200423173203871-980115964

Простыми словами:

  Возможно, приведенный выше процесс обновления и диаграмма процесса обновления немного сложны для понимания и немного запутаны. Мы можем сначала понять, почему существует процесс эскалации блокировок?HotSpotАвтор исследования обнаружил, что в большинстве случаев блокировка не только не конкурирует с несколькими потоками, но и всегда захватывается одним и тем же потоком несколько раз. Чтобы избежать потери производительности, вызванной получением и освобождением блокировок, вводится процесс эскалации блокировок. Понимание процесса эскалации блокировки требует ясности: блокировка будет обновлена ​​и не может быть понижена до тех пор, пока не произойдет конкуренция.

  Мы используем два потока T1, T2 для выполнения синхронизированного блока кода, чтобы продемонстрировать, как раздувается блокировка. Мы начинаем с состояния без блокировки.В это время T1 входит в блок кода синхронизации, чтобы определить состояние текущей блокировки. Выяснено, что это состояние без блокировки, которое будет использоваться в это время.CASИдентификатор потока в записи блокировки указывает на T1, а состояние отсутствия блокировки изменяется на смещенную блокировку. Поработав некоторое время, T2 вошел в блок кода синхронизации и обнаружил, что он уже смещен, поэтому я попытался использоватьCASИдите и попробуйте изменить идентификатор потока в записи блокировки на T2.Если изменение прошло успешно, T2 удерживает смещенную блокировку. Если это не удается, это означает, что есть конкуренция, и он модернизируется до облегченного замка.

  可能你会有疑问,为啥会失败呢? Мы хотимCASначать операцию,CASЭто сокращение от Compare-and-swap (сравните и замените), который является хорошо известным алгоритмом без блокировки. CAS должен иметь 3 операнда: адрес памяти V, старое ожидаемое значение A и целевое значение для обновления B. Другими словами, адрес памяти 0x01 хранит число 6, и я хочу превратить его в 7. В это время я сначала получаю значение 0x01 как 6, а затем снова получаю значение 0x01 и оцениваю, равно ли оно 6. Если это так, обновляю его до 7. Если нет, делаю это снова, пока оно не будет успешным. В основном это связано с квантами времени ЦП. Он может быть приостановлен на полпути выполнения, а затем другие потоки изменяют значение. В это время программа может установить неправильное значение, что приведет к ненормальным результатам.

Легко понятьCASТеперь давайте вернемся к процессу эскалации блокировки, T2 пытается использоватьCASЗаменив идентификатор потока в записи блокировки, результатCASFailed, значит, в это время T1 украл замок, который изначально принадлежал T2 Очевидно, этот момент случился.конкурироватьТак что замок надо модернизировать. Перед обновлением до облегченной блокировки смещенный поток T1 будет приостановлен и проверит состояние T1, если T1Неактивное состояние/выход из синхронизированного блока, T1 снимет блокировку смещения и выйдет из спящего режима. Если блок кода синхронизации не вышел, в это время он будет обновлен до облегченной блокировки, и блокировка будет получена T1, и выполнение продолжится с безопасной точки, а облегченная блокировка будет освобождена после выполнения.

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

  T1 какое-то время работает без сбоев, потому что никто не соревнуется. В определенный момент времени появляется T2 и используетCASБлокировка получена, но обнаруживается, что она терпит неудачу. В это время T2 будет ждать некоторое время (поверните, чтобы получить блокировку). Поскольку конкуренция не очень жесткая, после завершения выполнения T1 блокировка может получить и выполнить. Если замок не может быть получен в течение длительного времени, может возникнуть конкуренция.Там может быть T3, который крадет легкий замок, который изначально принадлежал T2, и в это время он будет повышен до тяжелого замка.

u=628528004,774370142&fm=26&gp=0

отступить после еды

интервьюер:Inner OS: Я его не спрашивал, похоже, он и не надеется, пусть возвращается и делает уведомления.

Сяочжай, верно? У меня есть общее представление о вашем уровне здесь, и я вполне доволен вами. Однако на нашей стороне все еще есть несколько кандидатов, которые еще не прошли собеседование, поэтому мы не можем дать вам прямой ответ. следует сначала вернуться и дождаться уведомления.

домик: Хорошо, спасибо, интервьюер, я сначала вернусь сюда. Благодаря моей полной подготовке я ответил на все ответы, и я должен быть в состоянии получить предложение.

timg

Анализ вопросов интервью.

public class Demo {

    public static void main(String[] args) {
        String str = null;
        str = str + "";
        System.out.println(str);
    }

}

ответnull, ранее мы узнали, что использование+Выполнение сплайсинга фактически преобразуется вStringBuilderиспользоватьappendспособ сращивания. Итак, давайте посмотримappendЛогика реализации метода понятна.

public AbstractStringBuilder append(String str) {
  if (str == null)
    return appendNull();
  int len = str.length();
  ensureCapacityInternal(count + len);
  str.getChars(0, len, value, count);
  count += len;
  return this;
}
private AbstractStringBuilder appendNull() {
  int c = count;
  ensureCapacityInternal(c + 4);
  final char[] value = this.value;
  value[c++] = 'n';
  value[c++] = 'u';
  value[c++] = 'l';
  value[c++] = 'l';
  count = c;
  return this;
}

Как видно из кода, если входящая строкаnullпри звонкеappendNullметод, в то время какappendNullвернет ноль.

конец

  Я другой технарь, каждый день я делаю небольшие успехи и живу другой жизнью. Увидимся в следующий раз!

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