Синхронизировать, чтобы гарантировать, что ссылка на неизменный объект будет видна другому потоку - PullRequest
3 голосов
/ 06 июля 2011

Я изучал это , чтобы понять поведение финальных полей в новом JMM (5 и далее).Эта концепция ясна: гарантированная видимость инициализированных конечных полей для всех потоков после правильного построения объекта.

Но затем в конце раздела я прочитал это, что просто смущает меня:

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

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

Если это так, хотя мы можем обмениваться инициализированными неизменяемыми объектами между потоками без каких-либо проблем, небезопасных для потока, но во время создания они нуждаются в «особой заботе» о безопасности потоков, как и о других изменяемых файлах?

Ответы [ 4 ]

5 голосов
/ 06 июля 2011

Семантика конечных полей, как определено в разделе 17.5 JLS , гарантирует, что:

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

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

Однако, нет никакой гарантии, что объект будет виден данному потоку.Это другая проблема.

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

Рассмотрим следующий код:

final class A {
  private final int x;
  A(int x) { this.x = x; }
  public getX() { return x; }
}

class Main {
  static volatile A a1 = null;
  static A a2 = null;
  public static void main(String[] args) {
    new Thread(new Runnable() { void run() { try {
      while (a1 == null) Thread.sleep(50);
      System.out.println(a1.getX()); } catch (Throwable t) {}
    }}).start()
    new Thread(new Runnable() { void run() { try {
      while (a2 == null) Thread.sleep(50);
      System.out.println(a2.getX()); } catch (Throwable t) {}
    }}).start()
    a1 = new A(1); a2 = new A(1);
  }
}

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

В этом коде мы можем быть уверены, что поток 1 завершит выполнение (чтото есть он увидит, что a1 != null. Однако может случиться так, что поток 2 остановится, так как он никогда не увидит запись в поле a2, так как он не является энергозависимым.

3 голосов
/ 06 июля 2011

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

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

Соответствующие части спецификации языка Java:

Два действия могут быть упорядочены случается до отношений.Если одно действие происходит до другого, то первое видно и упорядочено до второго.

и

Более конкретно, если двадействия разделяют отношения «происходит до», они не обязательно должны отображаться в том порядке, в каком они есть, в любом коде, с которым они не имеют отношения «происходит до».Запись в одном потоке, находящемся в гонке данных, с чтениями в другом потоке, например, может показаться неупорядоченной для этих чтений.

Случается до того, как может быть установлено несколькими способами:

Если у нас есть два действия x и y, мы пишем hb (x, y), чтобы указать, что x происходит до y.

  • Если x и yдействия того же потока и x идут перед y в программном порядке, а затем hb (x, y).
  • Имеется грань, предшествующая событию, от конца конструктора объекта до начала финализатора(§12.6) для этого объекта.
  • Если действие x синхронизируется со следующим действием y, то у нас также есть hb (x, y).
  • Если hb (x, y)и hb (y, z), затем hb (x, z).

, где

Синхронизирующие действия вызывают отношение синхронизированных действий, определенныхследующим образом:

  • Действие разблокировки на мониторе m синхронизируется со всеми последующими действиями блокировки на m (где последующие определены согласнов порядке синхронизации).
  • Запись в энергозависимую переменную (§8.3.1.4) v синхронизирует со всеми последующими чтениями v любым потоком (где последующие определены в соответствии с порядком синхронизации).
  • Действие, которое запускает поток, синхронизируется с первым действием в потоке, которое он запускает.
  • Запись значения по умолчанию (ноль, ложь или ноль) в каждую переменную синхронизируется с первойдействие в каждой теме.Хотя может показаться немного странным записать значение по умолчанию в переменную до того, как будет выделен объект, содержащий переменную, концептуально каждый объект создается в начале программы с его инициализированными значениями по умолчанию.
  • Последнее действиев потоке T1 синхронизируется с любым действием в другом потоке T2, которое обнаруживает, что T1 завершен.T2 может выполнить это, вызвав T1.isAlive () или T1.join ().
  • Если поток T1 прерывает поток T2, прерывание по T1 синхронизируется с любой точкой, где любой другой поток (включая T2) определяет, чтоT2 был прерван (сгенерировав InterruptedException или вызвав Thread.interrupted или Thread.isInterrupted).

Сделав поля окончательными, вы гарантируете, что их присвоение произойдет до завершение конструктора.Вам все еще нужно убедиться, что завершение конструктора происходит до доступа к объекту.Если этот доступ происходит в другом потоке, вам нужно установить synchronizations-с , используя любой из 6 способов, показанных выше.Обычно используются:

  1. Запустить поток чтения после завершения инициализации.На практике инициализация объекта в главном потоке перед запуском других потоков выполняет это прекрасно.
  2. Объявите поле, которое другие потоки используют для доступа к объекту volatile.Например:

    class CacheHolder {
        private static volatile Cache cache;
    
        public static Cache instance() {
            if (cache == null) {
                // note that several threads may get here at the same time,
                // in which case several caches will be constructed.
                cache = new Cache();
            }
            return cache;
        }
    }
    
  3. Выполните начальное назначение и чтение поля в синхронизированном блоке.

    class CacheHolder {
        private static Cache cache;
    
        public synchronized static Cache instance() {
            if (cache == null) {
                cache = new Cache();
            }
            return cache;
        }
    }
    
1 голос
/ 06 июля 2011

Заполнение всех полей final обеспечит их правильную публикацию в других потоках. Этот комментарий, вероятно, относится к следующему сценарию:

private myField;

public void createSomething()
{
    myField = new MyImmutableClass();
}

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

0 голосов
/ 06 июля 2011

Я полагаю, что автор ссылался на ситуацию, когда на неизменяемый объект ссылается не-1001 * поле.Если сама ссылка final, дополнительная синхронизация не требуется.
Дополнительное соображение заключается в том, что вышеизложенное относится только к полям объекта, которые инициализируются внутри конструктора объекта.

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