Java 8 接口方法中不允许“同步”的原因是什么?

在Java 8中,我可以很容易地写:

interface Interface1 {
    default void method1() {
        synchronized (this) {
            // Something
        }
    }

    static void method2() {
        synchronized (Interface1.class) {
            // Something
        }
    }
}

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

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

    static synchronized void method2() {
        // ^^^^^^^^^^^^ Modifier 'synchronized' not allowed here
    }
}

现在,人们可以争辩说,这两个接口的行为方式相同,除了在 和 on 上建立一个契约,这比它更强大。当然,我们也可能认为,实现不应该对具体的实现状态做出任何假设,或者这样的关键字根本无法发挥其作用。Interface2method1()method2()Interface1default

问题:

JSR-335专家组决定不支持接口方法的原因是什么?synchronized


答案 1

虽然乍一看似乎很明显,人们想要支持默认方法上的修饰符,但事实证明这样做是危险的,因此被禁止。synchronized

同步方法是方法的简写,其行为就像整个主体被封闭在一个块中,该块的锁定对象是接收器。将此语义扩展到默认方法似乎也是明智的;毕竟,它们也是具有接收器的实例方法。(请注意,方法完全是语法优化;它们不是必需的,它们只是比相应的块更紧凑。有一个合理的论点可以证明,这首先是一个过早的句法优化,同步方法造成的问题比它们解决的问题更多,但那艘船很久以前就航行了。synchronizedsynchronizedsynchronized

那么,为什么它们很危险呢?同步是关于锁定的。锁定是关于协调对可变状态的共享访问。每个对象都应该有一个同步策略,用于确定哪些锁保护哪些状态变量。(参见 Java 并发实践,第 2.4 节。

许多对象使用 Java 监视器模式 (JCiP 4.1) 作为其同步策略,其中对象的状态由其固有锁保护。这种模式没有什么神奇或特别之处,但它很方便,并且在方法上使用关键字隐式地假设了这种模式。synchronized

它是拥有状态的类,用于确定该对象的同步策略。但是接口并不拥有它们所混合的对象的状态。因此,在接口中使用 sync 方法假定存在特定的同步策略,但您没有合理的依据来假定该策略,因此很可能使用同步不会提供额外的线程安全性(您可能在错误的锁上进行同步)。这会让您错误地确信自己已经对线程安全执行了某些操作,并且没有错误消息告诉您假设了错误的同步策略。

为单个源文件一致地维护同步策略已经足够困难了。确保子类正确遵守其超类定义的同步策略甚至更难。尝试在这种松散耦合的类(一个接口和可能实现它的许多类)之间这样做几乎是不可能的,并且非常容易出错。

鉴于所有这些反对的论点,论据是什么?似乎它们主要是为了使接口表现得更像特征。虽然这是一个可以理解的愿望,但默认方法的设计中心是接口进化,而不是“Traits--”。在两者可以始终如一地实现的地方,我们努力这样做,但是如果一个与另一个发生冲突,我们必须选择有利于主要设计目标。


答案 2
public class ParentSync {

public synchronized void parentStart() {
    System.out.println("I am " + this.getClass() + " . parentStarting. now:" + nowStr());
    try {
        Thread.sleep(30000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("I am " + this.getClass() + " . parentFinished. now" + nowStr());
}

private String nowStr() {
    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
}
}


public class SonSync1 extends ParentSync {
public void sonStart() {
    System.out.println("I am " + this.getClass() + ". sonStarting,calling parent now ... ");
    super.parentStart();
    System.out.println("I am " + this.getClass() + ". sonFinished");
}
}



public class SonSync2 extends ParentSync {

public void sonStart() {
    System.out.println("I am " + this.getClass() + ". sonStarting,calling parent now ... ");
    super.parentStart();
    System.out.println("I am " + this.getClass() + ". sonFinished");
}
}



public class SyncTest {
public static void main(String[] args) throws Exception {

    new Thread(() -> {
        new SonSync1().sonStart();
    }).start();

    new Thread(() -> {
        new SonSync2().sonStart();
    }).start();

    System.in.read();
}
}

结果:

I am class com.common.interface18_design.whynotsync_onmethod.SonSync1. sonStarting,calling parent now ... 
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2. sonStarting,calling parent now ... 
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2 . parentStarting. now:2019-04-18 09:50:08
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1 . parentStarting. now:2019-04-18 09:50:08
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1 . parentFinished. now2019-04-18 09:50:38
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1. sonFinished
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2 . parentFinished. now2019-04-18 09:50:38
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2. sonFinished

(抱歉使用父类作为示例)

从结果中,我们可以知道父类锁是由每个子类拥有的,SonSync1和SonSync2对象具有不同的对象锁。每个锁都是独立的。所以在这种情况下,我认为使用父类中的同步或通用接口并不危险。任何人都可以对此进行更多解释吗?


推荐