По какой причине «синхронизация» не разрешена в методах интерфейса 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 в методах интерфейса?


person Lukas Eder    schedule 04.05.2014    source источник
comment
Synchronized — это поведение реализации, и оно изменяет конечный результат байт-кода, созданный компилятором, чтобы его можно было использовать рядом с кодом. В объявлении метода нет смысла. Должно сбивать с толку то, что производит компилятор, если синхронизация находится на уровне абстракции.   -  person Martin Strejc    schedule 04.05.2014
comment
@MartinStrejc: Это может быть объяснением пропуска default synchronized, но не обязательно для static synchronized, хотя я согласен, что последнее могло быть опущено из соображений согласованности.   -  person Lukas Eder    schedule 04.05.2014
comment
Я не уверен, добавляет ли этот вопрос какое-либо значение, поскольку модификатор synchronized может быть переопределен в подклассах, поэтому это имело бы значение только в том случае, если бы было что-то в качестве окончательных методов по умолчанию. (Ваш другой вопрос)   -  person skiwi    schedule 04.05.2014
comment
@skiwi: аргумента переопределения недостаточно. Подклассы могут переопределять методы, объявленные synchronized в суперклассах, эффективно удаляя синхронизацию. Я не удивлюсь, что отсутствие поддержки synchronized и отсутствие поддержки final связано, возможно, из-за множественного наследования (например, наследование void x() и synchronized void x() и т. д.). Но это предположение. Меня интересует авторитетная причина, если она есть.   -  person Lukas Eder    schedule 04.05.2014
comment
››Подклассы могут переопределять методы, объявленные синхронизированными в суперклассах, эффективно удаляя синхронизацию... только если они не вызывают super, что требует полной повторной реализации и возможного доступа к закрытым членам. Кстати, есть причина, по которой эти методы называются защитниками — они присутствуют, чтобы упростить добавление новых методов.   -  person bestsss    schedule 10.05.2014


Ответы (2)


Хотя поначалу может показаться очевидным, что хотелось бы поддерживать модификатор synchronized в методах по умолчанию, оказалось, что это было бы опасно, и поэтому было запрещено.

Синхронизированные методы — это сокращение для метода, который ведет себя так, как будто все тело заключено в блок synchronized, объектом блокировки которого является получатель. Может показаться разумным распространить эту семантику и на методы по умолчанию; в конце концов, они также являются методами экземпляра с получателем. (Обратите внимание, что методы synchronized — это полностью синтаксическая оптимизация; они не нужны, они просто более компактны, чем соответствующий блок synchronized. Можно привести разумный аргумент, что это была прежде всего преждевременная синтаксическая оптимизация, и что синхронизированные методы создают больше проблем, чем решают, но этот корабль уже давно уплыл.)

Так чем же они опасны? Синхронизация связана с блокировкой. Блокировка — это координация общего доступа к изменяемому состоянию. Каждый объект должен иметь политику синхронизации, которая определяет, какие блокировки защищают какие переменные состояния. (См. Параллелизм Java на практике, раздел 2.4.)

Многие объекты используют в качестве своей политики синхронизации Шаблон монитора Java (JCiP 4.1), в котором состояние объекта охраняется его внутренней блокировкой. В этом шаблоне нет ничего волшебного или особенного, но он удобен, и использование ключевого слова synchronized в методах неявно предполагает этот шаблон.

Именно класс, которому принадлежит состояние, определяет политику синхронизации этого объекта. Но интерфейсы не владеют состоянием объектов, с которыми они смешаны. Таким образом, использование синхронизированного метода в интерфейсе предполагает определенную политику синхронизации, но такую, для принятия которой у вас нет разумных оснований, так что вполне может быть так, что использование синхронизации не обеспечивает никакой дополнительной безопасности потоков (возможно, вы синхронизируете не ту блокировку). Это дало бы вам ложное чувство уверенности в том, что вы сделали что-то для безопасности потоков, и никакое сообщение об ошибке не говорит вам о том, что вы принимаете неправильную политику синхронизации.

Уже достаточно сложно последовательно поддерживать политику синхронизации для одного исходного файла; еще труднее обеспечить правильное соблюдение подклассом политики синхронизации, определенной его суперклассом. Попытка сделать это между такими слабо связанными классами (интерфейс и, возможно, множество классов, которые его реализуют) была бы почти невозможной и очень подверженной ошибкам.

Учитывая все эти аргументы против, что будет аргументом за? Кажется, они в основном предназначены для того, чтобы интерфейсы вели себя как трейты. Хотя это понятное желание, центр разработки методов по умолчанию — это эволюция интерфейса, а не «Черты--». Там, где можно было добиться последовательного достижения этих двух целей, мы стремились к этому, но там, где одно противоречило другому, нам приходилось выбирать в пользу основной цели дизайна.

person Brian Goetz    schedule 05.05.2014
comment
Также обратите внимание, что в JDK 1.1 модификатор метода synchronized появился в выходных данных javadoc, вводя людей в заблуждение, заставляя их думать, что это часть спецификации. Это было исправлено в JDK 1.2. Даже если он появляется в общедоступном методе, модификатор synchronized является частью реализации, а не контракта. (Аналогичные рассуждения и обработка произошли для модификатора native.) - person Stuart Marks; 05.05.2014
comment
Распространенной ошибкой в ​​ранних программах на Java было разбрасывание достаточного количества synchronized и потокобезопасных компонентов, и у вас была почти потокобезопасная программа. Проблема заключалась в том, что обычно это работало нормально, но ломалось неожиданным и хрупким образом. Я согласен с тем, что понимание того, как работает ваша блокировка, является ключом к надежным приложениям. - person Peter Lawrey; 05.05.2014
comment
@BrianGoetz Очень веская причина. Но почему synchronized(this) {...} разрешено в методе default? (Как показано в вопросе Лукаса.) Разве это не позволяет методу по умолчанию также владеть состоянием класса реализации? Разве мы не хотим предотвратить и это? Понадобится ли нам правило FindBugs, чтобы найти случаи, для которых это делают неосведомленные разработчики? - person Geoffrey De Smet; 05.05.2014
comment
@StuartMarks: Это действительно очень интересная деталь! Я не знал об этом изменении в выводе Javadoc - person Lukas Eder; 05.05.2014
comment
@Geoffrey: Нет, нет причин ограничивать это (хотя его всегда следует использовать с осторожностью). Блок синхронизации требует, чтобы автор явно выбрал объект блокировки; это позволяет им участвовать в политике синхронизации какого-либо другого объекта, если они знают, что это за политика. Опасная часть заключается в предположении, что синхронизация с «этим» (что и делают методы синхронизации) действительно имеет смысл; это должно быть более явное решение. Тем не менее, я ожидаю, что блоки синхронизации в методах интерфейса будут довольно редкими. - person Brian Goetz; 05.05.2014
comment
@GeoffreyDeSmet: По той же причине вы можете сделать, например. synchronized(vector). Если вы хотите быть в безопасности, вы никогда не должны использовать общедоступный объект (например, сам this) для блокировки. - person Yogu; 06.05.2014

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 имеют разные блокировки объекта. каждый замок является независимым. так что в этом случае я думаю, что не опасно использовать синхронизированный в родительском классе или общий интерфейс. может ли кто-нибудь объяснить больше об этом?

person zhenke zhu    schedule 18.04.2019