Java случается раньше и безопасность потоков
Предположим, у меня есть класс, который упаковывает HashMap следующим образом:
public final class MyClass{
private final Map<String, String> map;
//Called by Thread1
public MyClass( int size ){
this.map = new HashMap<String, String>( size );
}
//Only ever called by Thread2
public final String put( String key, String val ){
return map.put( key, value );
}
//Only ever called by Thread2
public final String get( String key ){
return map.get( key );
}
//Only ever called by Thread2
public final void printMap( ){
//Format and print the contents of the map
}
}
Этот класс инициализируется через "Thread1". Однако put, get, printMap и другие операции всегда вызываются только Thread2.
Правильно ли я понимаю, что этот класс является потокобезопасным, так как:
Поскольку ссылка на карту объявляется окончательной, все остальные потоки будут видеть начальное состояние карты (происходит до того, как установлено).
Поскольку put/get/printMap/etc всегда вызывается только Thread2, нет необходимости во взаимном исключении.
Спасибо
5 ответов
Так что вы спрашиваете, это правильное предположение. Вам не нужно делать это потокобезопасным, если вы можете гарантировать, что он используется только таким образом. Вы не можете передать половину построенного объекта в Java, поэтому "Конструктор может быть не завершен" не представляется возможным.
Так что если вы делаете
new Thread(new Runnable() {
public void run() {
final MyClass instance = new MyClass(10);
new Thread(new Runnable() {
public void run() {
instance.put("one", "one");
....
}
}).start();
}
}).start();
Вы в порядке:) Это то, что вы описали, созданный Thread1, но используется только Thread2. Нить не может столкнуться с собой.
Потокобезопасность - это другое определение, в котором составная сущность может безопасно взаимодействовать с несколькими потоками. В случае, который вы описали, такого сценария не происходит, так как у вас по существу есть поток, который создает, и другой, который манипулирует.
Это немного сложно ответить, потому что JLS не содержит концепцию поточно-ориентированного класса - все, что он определяет, это отношения между действиями (например, запись в поле, чтение из поля и т. Д.).
Тем не менее, по большинству определений, этот класс не является потокобезопасным - он содержит гонки данных, вызванные несинхронизированным доступом к не поточно-безопасной карте. Тем не менее, его использование является потокобезопасным, потому что вы безопасно публиковать this.map
в нить 2 после строительства, и в этот момент this.map
доступен только одному потоку, в этом случае безопасность потока не является проблемой.
Другими словами, это только немного больше, чем спрашивать, HashMap
Потокобезопасен, когда создан и доступен только в одном потоке. Ответ в этом случае таков: HashMap
не является потокобезопасным, но это не должно быть.
Точно так же ваш класс не является потокобезопасным, но, похоже, это не обязательно.
Первое: инициализация полей final и volatile в openJDK всегда происходит перед возвратом ссылки на объект конструктором. Изменение порядка не произошло. Тогда строительство объекта является потокобезопасным.
Второе: если методы put,get,printMap будут вызваны в Thread2, тогда взаимное исключение не требуется.
В вашем случае класс использования безопасен.
Если вы придерживаетесь своего определения того, что произойдет, то это будет на самом деле потокобезопасным. То есть только нитка-2 будет ставить и получать с карты.
Так как Map объявлен как final, вы устанавливаете отношение "происходит до" в отношении записи потока-1 и чтения потока-2.
а) Поскольку ссылка на карту объявлена окончательной, все остальные потоки увидят начальное состояние карты (происходит до того, как установлено).
Да. Пустой HashMap с size
указано.
б) Поскольку put/get/printMap/etc всегда вызывается только Thread2, нет необходимости во взаимном исключении.
Да, да, хотя такая логика обычно пугает меня на практике:)
Вопрос здесь не в самой реализации класса, а в том, является ли манипулирование описываемым вами классом поточно-ориентированным или нет, поэтому реализация класса на самом деле не имеет значения.
Я могу придумать 2 возможных способа доступа Thread2 к MyClass
пример.
- И Thread1, и Thread2 работают независимо. В этом случае это не потокобезопасно, потому что Thread2 может вызвать put/get/printMap/etc до завершения работы запущенного в Thread1 конструктора, что приводит к NPE.
MyClass
Экземпляр, к которому обращается Thread2, может иметь значение null в зависимости от того, как Thread2 обращается к нему. Если это общий экземпляр, когда Thread2 обращается к нему, он может быть нулевым, еслиMyClass
конструктор не завершил выполнение в Thread1. - Thread1 создает экземпляр и передает его в Thread2. В этом случае это потокобезопасный, но это на самом деле не имеет значения, потому что нет многопоточного доступа к этому экземпляру, потому что Thread2 должен ждать передачи экземпляра.
Таким образом, в ситуации, которую вы описываете, ответ на самом деле зависит не от реализации класса, а от того, как его экземпляр является общим. И в худшем случае (способ 1) это не потокобезопасно.