2016-02-03 3 views
2

Это пример от JCiP.Являются ли неизменяемые объекты иммунными к неправильной публикации?

public class Unsafe { 
    // Unsafe publication 
    public Holder holder; 

    public void initialize() { 
     holder = new Holder(42); 
    } 
} 

public class Holder { 
    private int n; 

    public Holder(int n) { 
     this.n = n; 
    } 
    public void assertSanity() { 
     if (n != n) { 
      throw new AssertionError("This statement is false."); 
     } 
    } 
} 

На странице 34:

[15] Проблема здесь не сам держатель класса, но что держатель не правильно опубликован. Тем не менее, Владелец может быть застрахован до ненадлежащей публикации, объявив n поле окончательным, что сделает Holder неизменным;

И от this answer:

спецификация для окончательного (см @ ответ andersoj в) гарантирует, что при возврате конструктора, окончательное поле было правильно инициализируется (как видно из всех потоков) ,

От wiki:

Например, в Java, если вызов конструктора имеет были встраиваемым то общие переменным могут быть немедленно обновляются один раз хранилище было выделено, но до встраиваемых конструктора инициализирует объект

Мой вопрос:

Потому что: (может быть неправильно, я не знаю.)

а) общие переменное может быть немедленно обновлен до встраиваемого конструктор инициализирует объект.

b) окончательное поле будет гарантированно правильно инициализировано (как видно из всех потоков) ТОЛЬКО при возврате конструктора.

Возможно ли, что другой поток видит значение по умолчанию holder.n? (т. е. другой поток получает ссылку на holder до того, как возвращается конструктор holder.)

Если да, то как вы объясните это утверждение ниже?

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

EDIT: От JCiP.Определение неизменного объекта:

Объект является неизменным, если:
х его состояние не может быть изменен после строительства;

х Все его поля являются окончательными; [12] и

х Это правильно построена (эта ссылка не избежать в процессе строительства).

Таким образом, по определению, неизменяемые объекты не имеют «this ссылка« экранирование »проблем. Правильно?

Но будут ли они страдать от Out-of-order writes в шаблоне с двойной проверкой, если не объявлены изменчивыми?

+0

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

+0

@scottb Неверный. Сам конструктор может утечка ссылки. – chrylis

ответ

4

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

Это достигается за счет final поля семантики, введенных в Java 5. Доступ к данным через конечного поля имеет более сильную семантику памяти, как это определено в jls-17.5.1

С точки зрения компилятора переупорядочения и барьеров памяти, есть больше ограничений, когда касающиеся окончательных полей, см. JSR-133 Cookbook. Переупорядочение вас беспокоит не произойдет.

И да - замок с двойной проверкой может быть выполнен через конечное поле в обертке; нет volatile не требуется! Но этот подход не обязательно быстрее, потому что необходимы два чтения.


Обратите внимание, что эта семантика применяется к отдельным конечным полям, а не всему объекту в целом. Например, String содержит изменяемое поле hash; тем не менее, String считается неизменным, поскольку его общественное поведение основано только на полях final.

Конечное поле может указывать на изменяемый объект. Например, String.value является char[], который является изменяемым. Нецелесообразно требовать, чтобы неизменный объект являлся деревом конечных полей.

final char[] value; 

public String(args) { 
    this.value = createFrom(args); 
} 

Пока мы не изменяем содержание value после конструктора выхода, это нормально.

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

public String(args) { 
    this.value = new char[1]; 
    this.value[0] = 'x'; // modify after the field is assigned. 
} 

Другой пример

final Map map; 
List list; 

public Foo() 
{ 
    map = new HashMap(); 
    list = listOf("etc", "etc", "etc"); 
    map.put("etc", list) 
} 

Любой доступ через последнее поле будет казаться неизменны, например, foo.map.get("etc").get(2).

Доступ не через окончательное поле - foo.list.get(2) небезопасен из-за неправильной публикации, хотя он читает одно и то же место назначения.


Это мотивы дизайна. Теперь давайте посмотрим, как JLS формализует его в jls-17.5.1

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

Обычная проблема небезопасной публикации - это отсутствие происшествий-до (hb) отношений. Даже если чтение видит запись, оно ничего не устанавливает w.r.t другими действиями. Но если волатильное чтение видит волатильную запись, JMM устанавливает hb и порядок среди многих действий.

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

A deferences() порядок ограничивает семантику для доступа до окончательное поле.

Давайте вернемся к Foo пример, чтобы увидеть, как это работает

tmp = new Foo() 

    [w] write to list at index 2 

    [f] freeze at constructor exit 

shared = tmp; [a] a normal write 

// Another Thread 

foo = shared; [r0] a normal read 

if(foo!=null) // [r0] sees [a], therefore mc(a, r0) 

    map = foo.map;   [r1] reads a final field 

    map.get("etc").get(2) [r2] 

Мы

hb(w, f), hb(f, a), mc(a, r1), and dereferences(r1, r2) 

поэтому w видна r2.


По существу, через Foo обертку, карта (которая изменчива сам по себе) публикуются безопасно, хотя небезопасных публикация ... если это имеет смысл.

Можем ли мы использовать оболочку, чтобы установить окончательную полевую семантику, а затем отбросить ее? Как

Foo foo = new Foo(); // [w] [f] 

shared_map = foo.map; // [a] 

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


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

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

-- class Bar 

final int x; 

Bar(int x, int ignore) 
{ 
    this.x = x; // assign to final 
} // [f] freeze action on this.x 

public Bar(int x) 
{ 
    this(x, 0); 
    // [f] is reached! 
    leak(this); 
} 

Это безопасно, насколько x обеспокоен; действие замораживания на x определяется в существовании конструктора, в котором назначается x. Вероятно, это было сделано для безопасного утечки this.

+0

Где я могу найти дополнительную информацию (например, некоторые книги?) О 2 частичных заказах: цепочка памяти 'mc()' и цепочка разыменования 'dereferences()'? Я всегда нахожу JLS довольно трудно понять. Спасибо! – du369

+0

Не знаю. Если автор пишет книгу о 'mc()' stuff, он не собирается продавать какую-либо копию, чтобы восстановить расходы на кофе. В конце концов, эти формализации не имеют большого значения. Мы следуем общим шаблонам использования и большим пальцам. Язык разработан для этого. Спектр написан для исполнителей. – ZhongYu

2

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

Два возможных маршрута для эталонной утечки - это если конструктор пытается зарегистрировать новый объект для обратных вызовов (например, как прослушиватель событий для некоторого параметра конструктора) или с реестром или, что более тонко, вызывает неконфигурирование метод, который переопределяется, чтобы делать то же самое.

+0

Я читал об этом разделе «эта ссылка, выходящая» в JCiP. Мой вопрос: кроме утечки внутри конструктора, есть ли другой способ, которым «эта ссылка» может уйти? Например, считается ли проблема с «двойной проверкой-блокировкой» примером «эта ссылка экранируется»? – du369

+0

@ du369 Двойная проверка блокировки - совершенно другая конструкция. Единственный способ для неконтролируемой ссылки на утечку - от конструктора, либо напрямую (передача или присвоение «этого»), либо косвенно (вызывающие методы сами по себе, имеющие доступ к «этому» и утечка их). – chrylis

+0

Утечка 'this' от конструкторов не является проблемой, которая является уникальной или уникальной для неизменяемых объектов. Любой объект, который получает ссылку на неполностью сконструированный экземпляр, может просматривать этот экземпляр в несогласованном или недействительном состоянии, изменяемом или нет. Предполагая, что «это» не просочилось, модель памяти Java позволяет презумпцию безопасной публикации для действительно неизменяемых объектов. – scottb

Смежные вопросы