在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
}
}
现在,可以认为这两个接口的行为是相同的,只是Interface2在method1()和method2()上建立了一个契约,这比Interface1做的要强一些。当然,我们也可能认为默认实现不应该对具体的实现状态做任何假设,或者这样的关键字根本起不到应有的作用。
问题:
JSR-335专家组决定不支持同步接口方法的原因是什么?
虽然一开始似乎很明显,人们希望在默认方法上支持同步修饰符,但事实证明这样做是危险的,因此被禁止了。
Synchronized methods are a shorthand for a method which behaves as if the entire body is enclosed in a synchronized block whose lock object is the receiver. It might seem sensible to extend this semantics to default methods as well; after all, they are instance methods with a receiver too. (Note that synchronized methods are entirely a syntactic optimization; they're not needed, they're just more compact than the corresponding synchronized block. There's a reasonable argument to be made that this was a premature syntactic optimization in the first place, and that synchronized methods cause more problems than they solve, but that ship sailed a long time ago.)
那么,它们为什么危险呢?同步是关于锁定的。锁定是关于协调对可变状态的共享访问。每个对象都应该有一个同步策略来确定哪些锁保护哪些状态变量。(请参阅Java并发实践,第2.4节。)
许多对象使用Java Monitor Pattern (JCiP 4.1)作为同步策略,其中对象的状态由其内在锁保护。这个模式没有什么神奇或特殊之处,但它很方便,并且在方法上使用synchronized关键字隐式地假定了这种模式。
It is the class that owns the state that gets to determine that object's synchronization policy. But interfaces do not own the state of the objects into which they are mixed in. So using a synchronized method in an interface assumes a particular synchronization policy, but one which you have no reasonable basis for assuming, so it might well be the case that the use of synchronization provides no additional thread safety whatsoever (you might be synchronizing on the wrong lock). This would give you the false sense of confidence that you have done something about thread safety, and no error message tells you that you're assuming the wrong synchronization policy.
为单个源文件一致地维护同步策略已经足够困难了;更难的是确保子类正确地遵循其父类定义的同步策略。在这样松散耦合的类(一个接口和可能实现它的许多类)之间尝试这样做几乎是不可能的,而且很容易出错。
既然有这么多反对的理由,那么支持的理由又是什么呢?它们似乎主要是为了让界面表现得更像特征。虽然这是一个可以理解的愿望,但默认方法的设计中心是接口进化,而不是“Traits——”。在这两个目标可以一致实现的地方,我们努力做到这一点,但在其中一个与另一个相冲突的地方,我们必须选择有利于主要设计目标的方案。
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对象拥有不同的对象锁。每个锁都是独立的。因此,在这种情况下,我认为在父类或公共接口中使用synchronized并不危险。谁能解释一下吗?