Статьи

Если бы Java была разработана сегодня: синхронизируемый интерфейс

Ява прошла долгий путь. Очень долгий путь. И это несет в себе весь «мусор» из ранних дизайнерских решений.

Одна вещь, о которой сожалели снова и снова, это то, что каждый объект (потенциально) содержит монитор . Это вряд ли когда-либо необходимо, и этот недостаток был исправлен, наконец, в Java 5, когда были представлены новые API параллелизма, такие как java.util.concurrent.locks.Lock и его подтипы. С тех пор написание синхронизированного параллельного кода стало намного проще, чем раньше, когда у нас были только ключевое слово synchronized и сложный для понимания механизм wait() и notify() :

Синхронизированный модификатор почти не используется

Оригинальный языковой дизайн, указанный для этих «удобных» модификаторов на методах:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
// These are the same:
public synchronized void method() {
    ...
}
 
public void method() {
    synchronized (this) {
        ...
    }
}
 
// So are these:
public static synchronized void method() {
    ...
}
 
public static void method() {
    synchronized (ClassOfMethod.class) {
        ...
    }
}

Вы вряд ли захотите синхронизировать всю область метода, чтобы сохранить минимальное время синхронизации, а выделение метода каждый раз, когда вам нужна синхронизация, утомительно.

Кроме того, монитор нарушает инкапсуляцию. Каждый может синхронизироваться на вашем мониторе, если вы синхронизируете на this или на всем class . Вы, вероятно, не хотите этого, поэтому большинство людей, которые все еще работают с synchronized ключевым словом, просто создадут явный частный объект блокировки, такой как:

01
02
03
04
05
06
07
08
09
10
11
12
13
class SomeClass {
    private Object LOCK = new Object();
 
    public void method() {
        ...
 
        synchronized (LOCK) {
            ...
        }
 
        ...
    }
}

Если это стандартный вариант использования для классических synchronized блоков, нужен ли нам монитор для каждого объекта?

Синхронизировано в более современной версии Java

Если бы Java была разработана с учетом сегодняшних знаний о языке Java, мы бы не позволили использовать synchronized для любого случайного объекта (включая строки или массивы):

1
2
3
4
// Wouldn't work
synchronized ("abc") {
    ...
}

Мы хотели бы ввести специальный интерфейс Synchronizable маркера, который гарантирует, что разработчики будут иметь монитор. И synchronized блок будет принимать только Synchronizable аргументы:

1
2
3
4
5
Synchronizable lock = ...
 
synchronized (lock) {
    ...
}

Это будет работать точно так же, как foreach или try-with-resources:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
Iterable<Object> iterable = ...
 
// The type to the right of ":" must be Iterable
for (Object o : iterable) {
    ...
}
 
// The assignment type must be AutoCloseable
try (AutoCloseable closeable = ...) {
    ...
}
 
// The assignment type must be a functional interface
Runnable runnable = () -> {};

Таким образом, для того, чтобы данная функция языка работала, язык Java накладывает ограничения на типы, которые используются в этом контексте. В случае foreach или try-with-resources требуется конкретный тип JDK. В случае лямбда-выражений требуется соответствующий структурный тип (который является довольно эзотерическим, но умным для Java).

К сожалению, по причинам обратной совместимости не будет добавлено никаких новых ограничений для synchronized блоков. Или будет там? Было бы замечательно, и дополнительное предупреждение могло бы быть выдано, если тип не Synchronizable . Это может позволить в течение нескольких будущих основных выпусков удалять мониторы из объектов, которые не обязательно должны быть синхронизируемыми.

По сути, это то, что язык C делал с мьютексами все время. Они особенная вещь. Не обычное дело.