Как уже упоминалось в своем ответе jontro (и в основном, как уже сказано в предупреждении): подобного рода синхронизация на объекте ShortCircuit
не имеет эффекта, которого разработчик, вероятно, надеялся достичь.К сожалению, всплывающая подсказка на скриншоте скрывает фактический код, но кажется, что код может быть примерно
synchronized (s)
{
if (!s.isFinalCallbackFired())
{
s.doFire();
}
}
То есть: сначала проверяется, возвращает ли isFinalCallbackFired
false
, и если этоВ этом случае что-то (скрытое) выполнено, что, вероятно, приводит к переключению состояния isFinalCallbackFired
на true
.
Итак, я предполагаю, что цель помещения оператора if
в блок synchronized
состояла в том, чтобы убедиться, что doFire
всегда вызывается точно один раз .
И действительно, на этом этапе синхронизация может быть оправдана.Точнее говоря, немного упрощенно:
Что может быть гарантировано :
Когда два потока выполняют метод fireFinalCallback
с одним и тем же параметром ShortCircuit
,Блок synchronized
гарантирует, что только один поток может одновременно проверять состояние isFinalCallbackFired
и (если это false
) вызывать метод doFire
.Таким образом, гарантируется, что doFire
будет вызываться только один раз .
Что не может быть гарантировано :
Когда один поток выполняет метод fireFinalCallback
, а другой поток выполняет любую операцию над объектом ShortCircuit
(например, вызовом)doFire
), тогда это может привести к противоречивому состоянию.В частности, если другой поток также выполняет
if (!s.isFinalCallbackFired())
{
s.doFire();
}
, но без синхронизации на объекте, то doFire
может вызываться дважды.
MCVE, который иллюстрирует эффект:
public class SynchronizeOnParameter
{
public static void main(String[] args)
{
System.out.println("Running test without synchronization:");
runWithoutSync();
System.out.println();
System.out.println("Running test with synchronization:");
runWithSync();
System.out.println();
System.out.println("Running test with wrong synchronization:");
runWithSyncWrong();
System.out.println();
}
private static void runWithoutSync()
{
ShortCircuit s = new ShortCircuit();
new Thread(() -> fireFinalCallbackWithoutSync(s)).start();
pause(250);
new Thread(() -> fireFinalCallbackWithoutSync(s)).start();
pause(1000);
}
private static void runWithSync()
{
ShortCircuit s = new ShortCircuit();
new Thread(() -> fireFinalCallbackWithSync(s)).start();
pause(250);
new Thread(() -> fireFinalCallbackWithSync(s)).start();
pause(1000);
}
private static void runWithSyncWrong()
{
ShortCircuit s = new ShortCircuit();
new Thread(() -> fireFinalCallbackWithSync(s)).start();
if (!s.isFinalCallbackFired())
{
s.doFire();
}
}
private static void fireFinalCallbackWithoutSync(ShortCircuit s)
{
if (!s.isFinalCallbackFired())
{
s.doFire();
}
}
private static void fireFinalCallbackWithSync(ShortCircuit s)
{
synchronized (s)
{
if (!s.isFinalCallbackFired())
{
s.doFire();
}
}
}
static class ShortCircuit
{
private boolean fired = false;
boolean isFinalCallbackFired()
{
return fired;
}
void doFire()
{
System.out.println("Calling doFire");
pause(500);
fired = true;
}
}
private static void pause(long ms)
{
try
{
Thread.sleep(ms);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
Выход составляет
Running test without synchronization:
Calling doFire
Calling doFire
Running test with synchronization:
Calling doFire
Running test with wrong synchronization:
Calling doFire
Calling doFire
Так что synchonized
block делает , чтобы убедиться, что метод doFire
звонил только один раз.Но это работает, только если все модификации выполняются только в методе fureFinalCallback
.Если объект изменяется в другом месте, без блока synchronized
, метод doFire
можно вызвать дважды.
(Я хотел бы предложить решение для этого, но без подробностей о ShortCircuit
class, а также оставшиеся классы и процессы, можно только дать смутный намек, чтобы взглянуть на пакет java.util.concurrent
и его подпакеты: блокировки и условия могут быть жизнеспособным путем, но вы должны понять, чтовне ...)