问题是,如果在代码的其他位置使用它时忘记同步,则可能会得到不可预测的结果。在类内部进行同步要好得多,这样可以保证它是线程安全的。ShortCircuit
ShortCircuit
更新
如果要将同步移动到类之外,则线程处理本质上是不安全的。如果要在外部同步它,则必须审核使用它的所有位置,这就是您收到警告的原因。这一切都与良好的封装有关。如果它是在公共API中,情况会更糟。
现在,如果将方法移动到类中,则可以保证回调不会同时触发。否则,在调用该类上的方法时,需要牢记这一点。fireFinalCallback
ShortCircuit
正如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
doFire
fureFinalCallback
synchronized
doFire
(我想为此提供一个解决方案,但是如果没有关于类和其余类和进程的详细信息,人们只能给出模糊的提示来看看java.util.concurrent
包及其子包:锁和条件可能是一个可行的路径,但你必须弄清楚...)ShortCircuit
-
如何使用Java中的RESTful Web服务获取远程/客户端IP地址? 我已经在我的项目中编写了Rest Web服务。Web服务调用可能来自不同 machine.so 我需要通过REST Web服务找出IP地址。 从这个请求.getRemoteAddr()使用这个。 但是我不能使用getRemoteAddr()。因为我的请
-
从包含大量文件的zip文件中提取1文件的最快方法是什么? 我尝试了但它们也缺少一些东西。 LZMA SDK不提供一种如何使用的文档/教程,这非常令人沮丧。没有 javadoc。 虽然7z jbinding没有提供一种简单的方法来只提取1个文件,但是,它只提供了提取zip文件
-
输入/输出流在销毁时是否关闭? Java 中的 InputStreams 和 OutputStreams 是否在销毁时关闭()?我完全理解这可能是不好的形式(特别是在C和C++世界中),但我很好奇。 另外,假设我有以下代码: 无名的FileInputStream是否在p.load
-
Java 程序中的字符串大小是否有任何限制? 我有一个字符串定义为 字符串 xx 我可以分配的字符数是否有任何限制? 2) 我正在将用户输入分配给此字符串 xx。70%的人只说一个字。有时他们给出一个大句子,所以想知道可
-