По какой причине синхронизация не допускается в интерфейсных методах 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
}
}
Теперь можно утверждать, что два интерфейса ведут себя одинаково, за исключением того, что Interface2
устанавливает договор о method1()
и на method2()
что немного сильнее чем что Interface1
делает. Конечно, мы также можем утверждать, что default
реализации не должны делать какие-либо предположения о конкретном состоянии реализации, или что такое ключевое слово просто не будет тянуть свой вес.
Вопрос:
По какой причине экспертная группа JSR-335 решила не поддерживать synchronized
по интерфейсным методам?
1 ответ
Это было преднамеренное решение, а не упущение (как было предложено в другом месте). Поначалу может показаться очевидным, что кто-то захочет поддержать synchronized
Модификатор по умолчанию методы, оказывается, что это было бы опасно, и поэтому было запрещено.
Синхронизированные методы - это сокращение для метода, который ведет себя так, как будто все тело заключено в synchronized
блок, чей объект блокировки является получателем. Может показаться целесообразным расширить эту семантику и на методы по умолчанию; в конце концов, они тоже методы экземпляра с получателем. (Обратите внимание, что synchronized
методы являются полностью синтаксической оптимизацией; они не нужны, они просто более компактны, чем соответствующие synchronized
блок. Есть разумный аргумент в пользу того, что это была преждевременная синтаксическая оптимизация, и что синхронизированные методы вызывают больше проблем, чем решают, но этот корабль давно проплыл.)
Итак, почему они опасны? Синхронизация о блокировке. Блокировка - это координация общего доступа к изменяемому состоянию. Каждый объект должен иметь политику синхронизации, которая определяет, какие блокировки защищают, какие переменные состояния. (См. Java Concurrency на практике, раздел 2.4.)
Многие объекты используют в качестве своей политики синхронизации шаблон монитора Java (JCiP 4.1), в котором состояние объекта защищается его внутренней блокировкой. В этом шаблоне нет ничего волшебного или особенного, но он удобен, и использование synchronized
Ключевое слово для методов неявно предполагает этот шаблон.
Это класс, которому принадлежит состояние, которое определяет политику синхронизации этого объекта. Но интерфейсам не принадлежит состояние объектов, в которые они смешаны. Поэтому использование синхронизированного метода в интерфейсе предполагает определенную политику синхронизации, но такую, которую у вас нет разумных оснований для принятия, поэтому вполне может быть так, что использование синхронизации не обеспечивает никакой дополнительной безопасности потока (вы можете синхронизироваться с неправильной блокировкой). Это даст вам ложное чувство уверенности в том, что вы что-то сделали с безопасностью потоков, и ни одно сообщение об ошибке не сообщит вам, что вы принимаете неправильную политику синхронизации.
Уже достаточно сложно постоянно поддерживать политику синхронизации для одного исходного файла; еще труднее обеспечить, чтобы подкласс правильно придерживался политики синхронизации, определенной его суперклассом. Попытка сделать это между такими слабо связанными классами (интерфейсом и, возможно, многими классами, которые его реализуют) была бы почти невозможной и подверженной ошибкам.
Учитывая все эти аргументы против, для чего будет аргумент? Похоже, они в основном заставляют интерфейсы вести себя как черты. Хотя это понятное желание, центром разработки методов по умолчанию является эволюция интерфейса, а не "черты". В тех случаях, когда эти два фактора могли быть последовательно достигнуты, мы стремились к этому, но в тех случаях, когда одно конфликтует с другим, нам приходилось выбирать в пользу основной цели проектирования.
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 имеют различную блокировку объекта. каждый замок независим. так что в этом случае, я думаю, не опасно использовать синхронизированный в родительском классе или общий интерфейс. Кто-нибудь может объяснить больше об этом?