Это кажется довольно простой проблемой, но я не могу найти четкое подтверждение.
Скажем, у меня есть класс, правильно синхронизированный сам по себе:
public class SyncClass {
private int field;
public synchronized void doSomething() {
field = field * 2;
}
public synchronized void doSomethingElse() {
field = field * 3;
}
}
Если мне нужно иметь ссылку для экземпляра этого класса, разделяемого между потоками, , мне все равно нужно объявить этот экземпляр volatile или final, я прав? Как в:
public class MainClass { // previously OuterClass
public static void main(String [ ] args) {
final SyncClass mySharedObject = new SyncClass();
new Thread(new Runnable() {
public void run() {
mySharedObject.doSomething();
}
}).start();
new Thread(new Runnable() {
public void run() {
mySharedObject.doSomethingElse();
}
}).start();
}
}
Или, если mySharedObject
не может быть окончательным, поскольку его инстанцирование зависит от некоторых других условий (взаимодействие с GUI, информация из сокета и т.д.), неизвестно заранее:
public class MainClass { // previously OuterClass
public static void main(String [ ] args) {
volatile SyncClass mySharedObject;
Thread initThread = new Thread(new Runnable() {
public void run() {
// just to represent that there are cases in which
// mySharedObject cannot be final
// [...]
// interaction with GUI, info from socket, etc.
// on which instantation of mySharedObject depends
if(whateverInfo)
mySharedObject = new SyncClass();
else
mySharedObject = new SyncClass() {
public void someOtherThing() {
// ...
}
}
}
});
initThread.start();
// This guarantees mySharedObject has been instantied in the
// past, but that still happened in ANOTHER thread
initThread.join();
new Thread(new Runnable() {
public void run() {
mySharedObject.doSomething();
}
}).start();
new Thread(new Runnable() {
public void run() {
mySharedObject.doSomethingElse();
}
}).start();
}
}
Финальные или летучие являются обязательными, тот факт, что MyClass
синхронизирует доступ к своим собственным членам, НЕ освобождается от необходимости следить за тем, чтобы ссылка была разделена между потоками. Правильно ли это?
Различия с Разница между изменчивой и синхронизированной в Java
1- Рассматриваемый вопрос касается синхронизации и волатильности в качестве альтернатив для одного и того же поля/переменной, мой вопрос заключается в том, как правильно использовать уже правильно синхронизированный класс (т.е. синхронизирован был выбран), учитывая последствия, которые необходимо учитывать вызывающим, возможно, используя volatile/final по ссылке уже синхронизированного класса.
2- Другими словами, упомянутый вопрос/ответы касаются блокировки/изменчивости. ОДИНОЧНЫЙ ОБЪЕКТ, мой вопрос: как я могу быть уверенным, что разные потоки действительно видят тот же объект? ПЕРЕД фиксацией/доступом к нему.
Когда первый ответ упомянутого вопроса явно ссылается на волатильную ссылку, он касается неизменяемого объекта без синхронизации. Второй ответ ограничивает себя примитивными типами. Я нашел их полезными (см. Ниже), но недостаточно полно, чтобы пролить любые сомнения на то, что я здесь даю.
3. Ответные ответы являются очень абстрактными и научными объяснениями по очень открытым вопросам, совершенно не содержащим никакого кода; как я заявил во вступлении, мне нужно четкое подтверждение фактического кода, ссылающегося на конкретную, хотя и довольно распространенную проблему. Они связаны, конечно, но так же, как учебник относится к конкретной проблеме. (Я действительно прочитал его, прежде чем открывать этот вопрос, и считаю его полезным, но мне все еще нужно обсудить конкретное приложение.) Если в текстовых книгах разрешены все проблемы/сомнения, которые люди могут применять, возможно, нам вообще не понадобится stackoverflow.
Учтите, что при многопоточности вы не можете "просто попробовать", вам нужно правильное понимание и быть уверенным в деталях, потому что условия гонки могут идти прямо тысячу раз, а затем идут ужасно неправильно тысячи + 1 раз.