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

What is the reason why “synchronized” is not allowed in Java 8 interface methods?

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

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Interface1 {
    default void method1() {
        synchronized (this) {
            // Something
        }
    }

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

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

1
2
3
4
5
6
7
8
9
interface Interface2 {
    default synchronized void method1() {
        //  ^^^^^^^^^^^^ Modifier 'synchronized' not allowed here
    }

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

现在,我们可以说,这两个接口的行为是一样的,只是Interface2method1()method2()上建立了一个契约,这个契约比Interface1强一些。当然,我们也可能会争辩说,default实现不应该对具体的实现状态做任何假设,或者这样的关键字根本不会影响它的重量。

问题:

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


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

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

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

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

它是拥有状态的类,用于确定对象的同步策略。但是接口并不拥有它们混合在其中的对象的状态。因此,在接口中使用synchronized方法会假定一个特定的同步策略,但您没有合理的假设依据,因此使用同步可能不会提供任何额外的线程安全(您可能在错误的锁上进行同步)。这将给您一种错误的自信感,即您已经对线程安全做了一些事情,并且没有错误消息告诉您假设的同步策略是错误的。

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

考虑到所有这些反对的论点,有什么理由呢?似乎他们主要是为了让界面表现得更像特征。虽然这是一个可以理解的需求,但是默认方法的设计中心是接口进化,而不是"特性--"。在这两个目标能够始终如一地实现的地方,我们努力做到这一点,但是在一个目标与另一个目标发生冲突的地方,我们必须选择有利于主要设计目标的目标。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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();
}
}

结果:

1
2
3
4
5
6
7
8
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对象具有不同的对象锁。每把锁都是独立的。所以在本例中,我认为在父类或公共接口中使用synchronized并不危险。有人能解释更多吗?