我的IDE (JetBrains IntelliJ IDEA)警告我要对方法参数进行同步,即使它总是对象。

该警告全文如下:
方法参数s‘的同步。检查信息:报告局部变量或参数的同步。当使用这种同步时,很难保证正确性。可以通过控制访问(例如,通过同步包装类)或通过字段上的同步来改进这样的代码。
我猜想,对于自动装箱,参数可能是一个被转换为对象的原语?虽然,对于自动装箱,我会假设它总是一个对象,但可能不是一个共享对象,这意味着它不会是共享同步。
有人知道为什么警告会出现吗?在我的例子中,ShortCircuit类型始终是一个对象,而且IDE应该能够知道这一点。
发布于 2019-02-19 21:27:44
问题是,如果您在代码的其他地方使用ShortCircuit时忘记在它上同步,您可能会得到不可预测的结果。在ShortCircuit类中同步要好得多,因此它保证线程安全。
更新
如果要将同步移出类之外,那么线程本身就不安全。如果您想在外部同步它,您将不得不审计它使用的所有地方,这就是为什么您会收到警告。这都是关于良好的封装。如果是在公共API中,情况会更糟。
现在,如果将fireFinalCallback方法移动到ShortCircuit类,则可以保证回调不会同时启动。否则,在调用该类上的方法时,需要记住这一点。
发布于 2019-02-20 20:26:22
正如jontro already mentioned in his answer (基本上,正如警告已经说过的那样):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块确实确保只调用doFire方法一次。但是,只有当所有的修改都是在fureFinalCallback方法中完成时,这才能起作用。如果在没有synchronized块的情况下在其他地方修改了对象,则可以调用两次doFire方法。
(我想为此提供一个解决方案,但如果没有关于ShortCircuit类以及其余类和进程的详细信息,人们只能给出一个模糊的提示,让我们看看java.util.concurrent包及其子包:锁和条件可能是一条可行的路径,但您必须弄清楚.)
https://stackoverflow.com/questions/54775142
复制相似问题