首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么在方法参数上同步“危险”?

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

Stack Overflow用户
提问于 2019-02-19 21:24:14
回答 2查看 7.2K关注 0票数 19

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

该警告全文如下:

方法参数s‘的同步。检查信息:报告局部变量或参数的同步。当使用这种同步时,很难保证正确性。可以通过控制访问(例如,通过同步包装类)或通过字段上的同步来改进这样的代码。

我猜想,对于自动装箱,参数可能是一个被转换为对象的原语?虽然,对于自动装箱,我会假设它总是一个对象,但可能不是一个共享对象,这意味着它不会是共享同步。

有人知道为什么警告会出现吗?在我的例子中,ShortCircuit类型始终是一个对象,而且IDE应该能够知道这一点。

EN

回答 2

Stack Overflow用户

发布于 2019-02-19 21:27:44

问题是,如果您在代码的其他地方使用ShortCircuit时忘记在它上同步,您可能会得到不可预测的结果。在ShortCircuit类中同步要好得多,因此它保证线程安全。

更新

如果要将同步移出类之外,那么线程本身就不安全。如果您想在外部同步它,您将不得不审计它使用的所有地方,这就是为什么您会收到警告。这都是关于良好的封装。如果是在公共API中,情况会更糟。

现在,如果将fireFinalCallback方法移动到ShortCircuit类,则可以保证回调不会同时启动。否则,在调用该类上的方法时,需要记住这一点。

票数 11
EN

Stack Overflow用户

发布于 2019-02-20 20:26:22

正如jontro already mentioned in his answer (基本上,正如警告已经说过的那样):ShortCircuit对象上的这种同步并没有开发人员可能希望达到的效果。不幸的是,屏幕截图中的工具提示隐藏了实际的代码,但看起来代码可能大致是

代码语言:javascript
复制
synchronized (s)
{
    if (!s.isFinalCallbackFired())
    {
        s.doFire();
    }
}

也就是说:首先检查isFinalCallbackFired是否返回false,如果是这样的话,就会执行一些(隐藏的)操作,这可能会导致isFinalCallbackFired状态切换到true

因此,我的假设大概是,将if语句放入synchronized块的目的是确保doFire总是被称为(一旦)。

事实上,在这一点上,同步是合理的。更具体地说,有些过于简化:

什么是可以保证的

当两个线程使用相同的fireFinalCallback参数执行ShortCircuit方法时,synchronized块将保证每次只有一个线程能够检查isFinalCallbackFired状态并(如果是false)调用doFire方法。因此,可以保证只调用一次doFire

不能保证的:

当一个线程正在执行fireFinalCallback方法,而另一个线程对ShortCircuit对象执行任何操作(比如调用doFire)时,这可能会导致不一致的状态。特别是,如果另一个线程也

代码语言:javascript
复制
if (!s.isFinalCallbackFired())
{
    s.doFire();
}

但是如果不对对象进行同步,那么doFire可能会被调用两次。

下面是一个MCVE,它说明了这一效果:

代码语言:javascript
复制
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();
        }
    }

}

输出是

代码语言:javascript
复制
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包及其子包:锁和条件可能是一条可行的路径,但您必须弄清楚.)

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54775142

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档