Предоставление времени восстановления для небезопасно опубликованного java.lang.String
java.lang.String только эффективно неизменяем. Брайан Гетц (Brian Goetz) из "Java Concurrency in Practice" сказал, что что-то вроде эффективно неизменяемых объектов будет поточно-ориентированным, только если будет безопасно опубликовано. Теперь, скажите, что я небезопасно публикую String следующим образом:
public class MultiThreadingClass {
private String myPath ="c:\\somepath";
//beginmt runs simultaneously on a single instance of MultiThreading class
public void beginmt(){
Holder h = new Holder();
h.setPath(new File(myPath)); //line 6
h.begin();
}
}
public class Holder {
private File path;
public void setPath(File path){
this.path = path;
}
public void begin(){
System.out.println(path.getCanonicalPath()+"some string");
}
}
В тот момент, когда MultiThreadingClass инициализируется с помощью своего конструктора, может случиться, что конструктор File в строке 6 может не увидеть значение myPath.
Затем, примерно через три секунды после создания небезопасно опубликованного объекта String, потоки в MultiThreadingClass все еще работают. Может ли еще быть шанс, что конструктор File может не увидеть значение myPath?
2 ответа
Ваше заявление, о котором вы задаете свой вопрос:
В тот момент, когда MultiThreadingClass инициализируется с помощью своего конструктора, может случиться, что конструктор File в строке 6 может не увидеть значение myPath.
Ответ сложный. Вам не нужно беспокоиться о массиве символов value
внутри String
объект. Как я уже упоминал в комментариях, потому что это final
поле, которое назначается в конструкторах, и потому String
не передает ссылку на себя перед назначением final
поле, оно всегда безопасно публикуется. Вам не нужно беспокоиться о hash
а также hash32
поля тоже. Они не публикуются безопасно, но могут иметь только значение 0 или действительный хэш-код. Если они все еще 0, метод String.hashCode
будет пересчитывать значение - это приведет к тому, что другие потоки будут пересчитывать hashCode, когда это уже было сделано ранее в другом потоке.
Ссылка myPath
в MultiThreadingClass
не безопасно опубликовано, потому что это не final
, "В тот момент, когда MultiThreadingClass инициализируется с помощью своего конструктора", но также позже, после завершения работы конструктора, другие потоки, кроме потока, который выполнял конструктор, могут увидеть значение null
в myPath
а не ссылка на вашу строку.
Есть пример в разделе "Модель памяти Java" в спецификации языка Java [версия 8 связана, но это не изменилось, так как JMM был выпущен в JSR-133]:
Пример 17.5-1. последние поля в модели памяти Java
Программа ниже показывает, как конечные поля сравниваются с обычными полями.
class FinalFieldExample { final int x; int y; static FinalFieldExample f; public FinalFieldExample() { x = 3; y = 4; } static void writer() { f = new FinalFieldExample(); } static void reader() { if (f != null) { int i = f.x; // guaranteed to see 3 int j = f.y; // could see 0 } } }
Класс FinalFieldExample имеет конечное поле int x и нефинальное поле int y. Один поток может выполнить средство записи метода, а другой может выполнить средство чтения метода.
Поскольку метод writer записывает f после завершения конструктора объекта, метод reader будет гарантированно видеть правильно инициализированное значение для fx: он будет считывать значение 3. Однако fy не является окончательным; метод чтения поэтому не гарантированно видеть значение 4 для него.
Это даже может произойти на сильно загруженной машине со многими потоками.
Обходные / решения:
- Делать
myPath
final
поле (и не добавляйте конструкторы, которые выдаютthis
ссылка перед назначением поля) - Делать
myPath
volatile
поле - Убедитесь, что все потоки обращаются
myPath
синхронизировать на том же объекте монитора перед доступомmyPath
, Например, делаяbeginmt
synchronized
метод или любым другим способом.
Может ли еще быть шанс, что конструктор File может не увидеть значение myPath?
Ответ - да, это возможно, поскольку Java Memory Model гарантирует видимость только конечных полей:
"Должна быть обеспечена новая гарантия безопасности инициализации. Если объект построен правильно (что означает, что ссылки на него не исчезают во время построения), то все потоки, которые видят ссылку на этот объект, также увидят значения для его конечных полей. которые были установлены в конструкторе, без необходимости синхронизации ".
Однако я чувствую, что эту ситуацию невозможно воссоздать (я тоже раньше пробовал подобную теорию, но тщетно).
Существует случай небезопасной публикации / удаления этой ссылки в конструкторе, что может привести к неправильной инициализации сценария myPath. Пример этого приведен в листинге 3.7 упомянутой вами книги. Ниже приведен пример того, как сделать ваш класс этой ссылкой для экранирования в конструкторе.
public class MultiThreadingClass implements Runnable{
public static volatile MultiThreadingClass unsafeObject;
private String myPath ="c:\\somepath";
public MultiThreadingClass() {
unsafeObject = this;
.....
}
public void beginmt(){
Holder h = new Holder();
h.setPath(new File(myPath)); //line 6
h.begin();
}
}
Приведенный выше класс может привести к тому, что другие потоки получат доступ к ссылке unsafeObject даже до правильной установки myPath, но повторное создание этого сценария может оказаться затруднительным.