为什么在Java 8接口方法中不允许使用"synchronized"的原因是什么?

8 浏览
0 Comments

为什么在Java 8接口方法中不允许使用"synchronized"的原因是什么?

在Java 8中,我可以轻松地写出:\n

interface Interface1 {
    default void method1() {
        synchronized (this) {
            // Something
        }
    }
    static void method2() {
        synchronized (Interface1.class) {
            // Something
        }
    }
}

\n我将获得完整的同步语义,也可以在类中使用。然而,我不能在方法声明中使用synchronized修饰符:\n

interface Interface2 {
    default synchronized void method1() {
        //  ^^^^^^^^^^^^ Modifier 'synchronized' not allowed here
    }
    static synchronized void method2() {
        // ^^^^^^^^^^^^ Modifier 'synchronized' not allowed here
    }
}

\n现在,我们可以争论两个接口的行为方式相同,只是Interface2method1()method2()建立了一个比Interface1更强的约定。当然,我们也可以争论default实现不应该对具体实现状态做出任何假设,或者这样一个关键字根本无法发挥作用。\n

问题:

\n为什么JSR-335专家组决定不支持在接口方法上使用synchronized

0
0 Comments

在Java 8中,不允许在接口方法中使用"synchronized"关键字的原因是因为接口的设计初衷是为了定义一组抽象方法,以便多个类可以实现这些方法来提供不同的实现。接口本身不应该包含具体的实现细节,而"synchronized"关键字是用于实现同步访问的具体实现细节。

在给定的代码示例中,我们可以看到父类`ParentSync`中的`synchronized`关键字修饰的`parentStart()`方法。`SonSync1`和`SonSync2`是`ParentSync`的子类,它们分别调用了父类的`parentStart()`方法。从输出结果可以看出,`SonSync1`和`SonSync2`对象拥有不同的对象锁,并且每个锁都是独立的。因此,在这种情况下,在父类或通用接口中使用`synchronized`关键字是不危险的。

然而,尽管在这个特定的例子中看起来是安全的,在接口方法中使用`synchronized`关键字可能会导致潜在的问题。因为接口可以被多个类实现,每个实现类都有自己的锁,可能会导致死锁或其他并发问题。因此,Java 8不允许在接口方法中使用`synchronized`关键字,以防止这些潜在问题的发生。

解决此问题的方法是在具体实现类中使用`synchronized`关键字,而不是在接口方法中使用。这样可以确保每个实现类拥有自己的对象锁,从而避免潜在的并发问题。

总之,Java 8不允许在接口方法中使用`synchronized`关键字的原因是为了避免并发问题的发生。解决此问题的方法是在具体实现类中使用`synchronized`关键字来保证线程安全。

0
0 Comments

在Java 8接口方法中不允许使用"synchronized"的原因是因为这样做是危险的,所以被禁止。同步方法是一个简写形式,它的行为就像整个方法体被包含在一个以接收者为锁对象的同步块中。将这种语义扩展到默认方法似乎是有道理的;毕竟,它们也是具有接收者的实例方法。(需要注意的是,同步方法完全是一种语法优化;它们并不是必需的,它们只是比相应的同步块更紧凑。可以说,这是一种过早的语法优化,同步方法带来的问题比解决的问题还要多,但这已经是很久以前的事情了。)

那么,为什么同步方法是危险的呢?同步是关于锁定的。锁定是关于协调对可变状态的共享访问的。每个对象应该有一个同步策略,确定哪些锁保护哪些状态变量。(参见《Java并发实践》,2.4节)许多对象使用Java监视器模式(JCiP 4.1)作为它们的同步策略,在这种模式中,一个对象的状态由其内在锁保护。这种模式并没有什么神奇或特殊之处,但它很方便,而且在方法上使用"synchronized"关键字隐式地假定了这种模式。

拥有状态的类决定了该对象的同步策略。但接口并不拥有其混入的对象的状态。因此,在接口中使用同步方法假定了特定的同步策略,但是你没有合理的基础来假设这一点,所以很可能同步提供的线程安全性根本不存在(你可能正在对错误的锁进行同步)。这会让你错误地认为你已经对线程安全性做了一些事情,而没有错误消息告诉你正在假设错误的同步策略。

维护单个源文件的同步策略已经很难了;确保子类正确遵守其超类定义的同步策略更加困难。在这两个松散耦合的类之间进行这样的操作(一个接口和可能实现它的多个类)几乎是不可能的,而且容易出错。

鉴于所有这些反对意见,有什么理由支持它呢?似乎它们主要是为了使接口更像特性。虽然这是一个可以理解的愿望,但默认方法的设计中心是接口演化,而不是"特性--"。在两者可以一致实现的情况下,我们努力这样做,但在两者相互冲突的情况下,我们必须选择支持主要的设计目标。

还需要注意的是,在JDK 1.1中,"synchronized"方法修饰符出现在javadoc的输出中,让人误以为它是规范的一部分。这在JDK 1.2中得到了修复。即使它出现在公共方法上,"synchronized"修饰符也是实现的一部分,而不是合同的一部分。(类似的推理和处理也适用于"native"修饰符。)

早期Java程序中的一个常见错误是在各个地方使用足够的"synchronized"和线程安全组件,从而得到一个几乎是线程安全的程序。问题是,这通常运行良好,但会以令人惊讶和脆弱的方式失败。我同意理解锁定如何工作是构建健壮应用程序的关键。

非常好的理由。但是为什么"default"方法中允许使用"synchronized(this) {...}"呢?(正如Lukas的问题所示。)这不也允许默认方法拥有实现类的状态吗?我们难道也不想阻止这种情况吗?我们是否需要一个FindBugs规则来找到不知情的开发人员这样做的情况?:这是一个非常有趣的细节!我不知道javadoc输出中的这个变化:不,没有理由限制这样做(虽然应该谨慎使用)。同步块要求作者显式选择一个锁对象;如果他们知道某个对象的同步策略,这使他们能够参与该对象的同步策略。危险的部分是假设对"this"进行同步(这是同步方法所做的),这需要更明确的决策。也就是说,我希望接口方法中的同步块非常少见。:出于相同的原因,你可以这样做:synchronized(vector)。如果你想要安全,你应该永远不要使用公共对象(如this本身)进行锁定。

0