Чому вихідний код JDK робить `остаточну` копію` нестабільних` екземплярів


74

Я прочитав вихідний код JDK про ConcurrentHashMap.

Але наступний код мене збентежив:

public boolean isEmpty() {
    final Segment<K,V>[] segments = this.segments;
    ...
}

Моє запитання:

"this.segments" оголошено:

final Segment<K,V>[] segments;

Отже, тут, на початку методу, декларований посилання того самого типу, вказує на ту саму пам’ять.

Чому автор написав це так? Чому вони не використовували this.segments безпосередньо? Є якась причина?

Відповіді:


94

Це ідіома, характерна для коду без блокування, що включає volatileзмінні. У першому рядку ви читаєте volatileодин раз, а потім працюєте з ним. Тим часом інший потік може оновити volatile, але вас цікавить лише значення, яке ви спочатку прочитали.

Крім того, навіть коли змінна члена, про яку йде мова, не є мінливою, а остаточною, ця ідіома пов’язана з кешами центрального процесора, оскільки читання з місця стека є більш зручним для кешування, ніж читання з випадкового місця кучі. Існує також більша ймовірність того, що локальний var в кінцевому підсумку буде прив'язаний до реєстру процесора.

У цьому останньому випадку насправді є певні суперечки, оскільки компілятор JIT зазвичай опікується цими проблемами, але Даг Ліа - один із хлопців, який дотримується цього на загальних принципах.


Отже, якщо хтось змінить this.segmentsвміст, ви не побачите цих змін у своєму segments?
бримборій

Звичайно ви це побачите. Але якщо хтось призначить щось інше segments, ви, очевидно, будете від цього ізольовані.
Марко Топольник

3
Ах так, звичайно. Неправильно зрозумів вашу відповідь ...;)
бримборій

оскільки сегменти (змінна екземпляра) оголошено остаточним, він не може змінитися? Я пам’ятаю, що додавання змінної loval superfluos до подвійно перевіреної ідіоми може пришвидшити виконання в деяких vms. Можливо, це подібний випадок. (@Marko вже додав, що з більш детальним поясненням, дякую!)
Пираня

6
Переглядаючи змінну, зчитувану ізольовано від усього іншого, пришвидшення дійсно може бути значним, але навіть найгірший результат операції зчитування, як правило, заглушується всіма іншими аспектами коду. Ось чому в реальному додатку ви навряд чи коли-небудь помітите різницю.
Марко Топольник

19

Я думаю, це для розгляду продуктивності, так що нам потрібно отримати значення поля лише один раз.

Ви можете послатись на однотонну ідіому з ефективної Java Джошуа Блоха

Його синглтон тут:

private volatile FieldType field;
FieldType getField() {
  FieldType result = field;
  if (result == null) { 
    synchronized(this) {
      result = field;
      if (result == null) 
        field = result = computeFieldValue();
    }
  }
  return result;
}

і він написав:

Цей код може виглядати трохи заплутаним. Зокрема, потреба в результаті локальної змінної може бути незрозумілою. Що ця змінна робить, щоб забезпечити читання поля лише один раз у загальному випадку, коли воно вже ініціалізоване. Хоча це не є суворо необхідним, це може покращити продуктивність і є більш елегантним за стандартами, що застосовуються до одночасного програмування на низькому рівні. На моїй машині описаний вище спосіб приблизно на 25 відсотків швидший за очевидну версію без локальної змінної .


4

Це може зменшити розмір байтового коду - доступ до локальної змінної коротший у байтовому коді, ніж доступ до змінної екземпляра.

Накладні витрати на оптимізацію виконання можуть також бути зменшені.

Але жодне з них не є суттєвим. Це більше стосується стилю коду. Якщо вам зручно користуватися змінними екземпляра, неодмінно. Дуг Леа, мабуть, почуває себе комфортніше, маючи справу з місцевими змінними.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.