我的IDE(JetBrains IntelliJ IDEA)警告我有关在方法参数上进行同步,即使该参数始终是对象。
完整警告如下:
与方法参数“ s”同步...检查 info:报告有关局部变量或参数的同步。它是 当这种同步是 用过的。通过控制 通过例如访问同步的包装器类,或通过同步
我的猜测是,使用自动装箱后,参数可能是被转换为对象的原语?尽管使用自动装箱,我会假定它始终是一个对象,但可能不是共享对象,这意味着它不会被共享同步。
谁知道为什么会出现警告?在我的情况下,ShortCircuit
类型始终是对象,IDE应该能够知道。
答案 0 :(得分:7)
问题是,如果在代码的其他位置使用ShortCircuit
时忘记在ShortCircuit
上进行同步,则可能会得到不可预测的结果。在fireFinalCallback
类内部进行同步要好得多,以确保它是线程安全的。
更新
如果要将同步移到类之外,则对于线程来说本质上是不安全的。如果要在外部进行同步,则必须审核其使用的所有位置,因此才得到警告。这都是关于良好的封装。如果使用公共API,情况将更糟。
现在,如果将ShortCircuit
方法移至server {
listen 80;
server_name example.com;
# This is the "last resort" nginx will direct to when no other matches with location have been found.
# It will only look for a file named index.html
location / {
root /location/of/angular;
index index.html;
try_files $uri $uri/ /index.html;
}
# All requests that start with /api are directed to the laravel location.
# It will only look for a file named index.php
location /api {
alias /location/of/laravel;
index index.php;
try_files $uri $uri/ /index.php?$query_string;
}
# All files that end with .php are through fastcgi
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $request_filename;
fastcgi_param APP_ENV dev;
fastcgi_pass 127.0.0.1:9000;
}
}
类,则可以保证不会同时触发该回调。否则,在调用该类的方法时需要牢记这一点。
答案 1 :(得分:1)
如jontro already mentioned in his answer(基本上,正如警告所言):ShortCircuit
对象上的这种同步不会产生开发人员可能希望实现的效果。不幸的是,屏幕快照中的工具提示会隐藏实际的代码,但看起来代码可能大致是
synchronized (s)
{
if (!s.isFinalCallbackFired())
{
s.doFire();
}
}
也就是:首先检查isFinalCallbackFired
是否返回false
,如果是这种情况,则执行某些操作(隐藏),这很可能导致isFinalCallbackFired
状态切换到true
。
因此,我的粗略假设是,将if
语句放入synchronized
块的目的是确保始终总是准确地调用doFire
一次 strong>。
事实上,在这一点上,同步是合理的。更具体地说,有点过分简化了:
可以保证什么:
当两个线程使用相同的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
类以及其余类和进程的详细信息,则只能给出含糊的暗示,以了解{{3} }软件包及其子软件包:锁定和条件可能是一条可行的路径,但是您必须弄清楚这一点...)