在Java中何时使用同步

在Java中何时使用同步,java,android,synchronization,Java,Android,Synchronization,我希望这将是足够的信息,就这样吧。如果你需要更多信息,请在评论中告诉我 我有一个类,它有两个内部类。内部类都有两个方法调用外部类中的方法。所以,看起来是这样的: public OuterClass { private boolean outerMethodHasBeenCalled = false; private void outerMethod() { if(!outerMethodHasBeenCalled) { // do stu

我希望这将是足够的信息,就这样吧。如果你需要更多信息,请在评论中告诉我

我有一个类,它有两个内部类。内部类都有两个方法调用外部类中的方法。所以,看起来是这样的:

public OuterClass {
    private boolean outerMethodHasBeenCalled = false;

    private void outerMethod() {
        if(!outerMethodHasBeenCalled) {
            // do stuff
        }

        outerMethodHasBeenCalled = true;
    }

    private FirstInnerClass {
        public void someMethod() {
            outerMethod();
        }
    }

    private SecondInnerClass {
        public void someOtherMethod() {
            outerMethod();
        }
    }
}
需要注意的是:

  • 这是一款Android应用程序。
    FirstInnerClass
    SecondInnerClass
    的实例作为JavaScript接口传递给WebView,因此
    someMethod
    someOtherMethod
    可以在任何时候调用,无需特定顺序
  • 我目前对现有代码(没有synchronized关键字)有一个问题,其中
    outerMethod
    几乎是在同一时间被不同的对象调用的(我打印了一条日志消息,它们的时间戳是1000秒)。然后,我的应用程序两次“执行任务”,因为调用
    outerMethodHasBeenCalled
    时,
    outerMethodHasBeenCalled
    仍然为false。这不好,这正是我想要阻止的。我的应用程序应该只“做东西”一次,而且只能做一次:第一次调用
    outerMethod
  • 听起来好像我有多个
    OuterClass
    实例,但请放心,这只是
    OuterClass
    的一个实例
重要的是,我的应用程序只在第一次调用
outerMethod
时才“执行任务”(我希望这一点现在已经很明显)。所有后续调用基本上都被忽略。无论哪个内部类首先调用
outerMethod
,都无关紧要


因此,在这种情况下使用synchronized关键字是否合适?

将您只想在同步块中运行一次的所有内容包装在
outerMethod
中:

private void outerMethod() {
    synchronized (this) {
        if(!outerMethodHasBeenCalled) {
            // do stuff
        }

        outerMethodHasBeenCalled = true;
    }
}

这样,第一次调用该方法时,一次只允许一个线程进入同步块。第一个将执行if语句中的代码,然后将
outermethodhasbeencall
设置为
true
。其他线程将看到这是真的,并跳过if代码。

是的,根据您上面列出的内容,我同意:

private synchronized void outerMethod() {
...
}
注意,这将产生一个副作用,即在outerMethod()完成之前阻止其中一个调用者。如果这是可以接受的,酷。如果目的仅仅是让outerMethod()中的代码运行一次,并且如果第一个调用方正在运行outerMethod(),则第二个调用方可以不延迟,那么您可以考虑:

public OuterClass {
    private AtomicBoolean outerMethodHasBeenCalled = new AtomicBoolean();

    private void outerMethod() {
        if (outerMethodHasBeenCalled.compareAndSet(false, true)) {
            // do stuff
        }
    }
...

查看以了解那里发生了什么(假设它在Android的Java中可用)。

您是否还需要将标志
设置为volatile
(以便其他线程可以可靠地看到更改)?为了安全起见,可以使用AtomicBoolean。@Thilo:如果对标志的所有访问都在同步块内,则无需使用
volatile
。Java内存模型确保更改是可见的。AtomicBoolean和volatile在您不想承担完全同步块的成本时非常有用,尽管它们使用起来比较复杂。@不,如果访问outerMethodHasBeenCalled的唯一位置是在outerMethod中,您不需要这样做。synchronized关键字表示内存同步障碍。阅读synchronized关键字上的VM规范。顺便说一句,请不要只是为了“安全起见”而做事情。准确理解你所写的内容及其含义。其他在你理解之后进入你的代码的程序员将暂时被跟踪,试图找出使用构造的原因,例如,“他一定是因为某个原因而使用volatile的,我没有看到什么?”“顺便说一句,请不要只为了‘安全起见’而做任何事情。确切地理解你正在编写的内容及其含义。”为了安全起见,我并不是建议使用volatile。恰恰相反:因为低级原语太复杂了,凡人都无法理解(有时在它们的实现中会出现错误),所以我喜欢完全远离
synchronized
volatile
,使用java.util.concurrent中更用户友好的高级工具“为了安全起见”。