为什么在方法参数上同步“危险”

时间:2019-02-19 21:24:14

标签: java intellij-idea jetbrains-ide

我的IDE(JetBrains IntelliJ IDEA)警告我有关在方法参数上进行同步,即使该参数始终是对象。

enter image description here

完整警告如下:

  

与方法参数“ s”同步...检查   info:报告有关局部变量或参数的同步。它是   当这种同步是   用过的。通过控制   通过例如访问同步的包装器类,或通过同步   

我的猜测是,使用自动装箱后,参数可能是被转换为对象的原语?尽管使用自动装箱,我会假定它始终是一个对象,但可能不是共享对象,这意味着它不会被共享同步。

谁知道为什么会出现警告?在我的情况下,ShortCircuit类型始终是对象,IDE应该能够知道。

2 个答案:

答案 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} }软件包及其子软件包:锁定和条件可能是一条可行的路径,但是您必须弄清楚这一点...)