Вопрос по synchronized

311
10 февраля 2017, 03:27

Здравствуйте, вопрос по ключевому слову synchronized.

Я правильно понимаю, что если начал выполняться synchronized блок кода, пока он не выполнится, java не перескочит на другой поток?

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

Answer 1

Блок synchronized в Java обладает тем свойством, что если в этот блок вошёл один поток, то другой поток сможет войти в этот блок не раньше, чем из него выйдет первый. Проще говоря, если один поток выполняет блок synchronized, другие потоки, желающие выполнить этот блок, будут ожидать.

Иногда считают, что если один поток вошёл в synchronized-блок, то все другие потоки приостанавливаются. Это неверно. Другие потоки продолжают выполняться до тех пор, пока они не "наткнутся" на этот же блок.

Но на самом деле всё немного сложнее. Блок synchronized использует в качестве "барьера" некоторый объект (что-то, являющееся потомком java.lang.Object, поэтому на int синхронизироваться не удастся). Если объявить два блока с синхронизацией на одном и том же объекте, то если один блок выполняется, другие потоки не смогут войти ни в один из этих блоков. Пример:

Object o;
synchronized(o) {
    // Блок 1
}
synchronized(o) {
    // Блок 2
}

Если какой-либо поток выполняет блок 1, то никакой поток не сможет в это же время выполнить блок 2. И наоборот, если какой-либо поток выполняет блок 2, то никакой поток не сможет в это же время выполнить блок 1.

Также следует отметить, что у класса могут быть synchronized-методы:

class Test {
    public synchronized void f() { /* ... */ }
    public synchronized void g() { /* ... */ }
}

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

Перейдём теперь ко второй части вопроса. Почему инициализацию переменной выносят в synchronized-блок? Потому что утверждение об атомарности одной Java-операции неверно. Даже следующий оператор

int i = 0;

в общем случае может не быть атомарным. Дело в том, что атомарность означает невозможность "увидеть" промежуточное состояние операции. Например, вышеуказанный код может быть скомпилирован в две процессорных инструкции:

xor eax,eax
mov [i], eax

Если другой поток "вклинится" между xor и mov, то он "увидит" промежуточное состояние. Что уж говорить про более сложные инициализации типа

String s = "строка";

и уж тем более

Object o = big_ang_long_running_function();
Answer 2

Нет. В конструкции synchronized передается указатель на некий объект. Так вот Java гарантирует, что при вызове такого блока, второй поток, который вызовет synchronized с тем же объектом, заснет, пока не выполнится первый блок

READ ALSO
Почему мой Parcelable возвращает null?

Почему мой Parcelable возвращает null?

Дано: при нажатии на кнопку из адаптера во фрагмент передается Parcelable:

339
Эффективный дебаг рекурсии

Эффективный дебаг рекурсии

Хочу создать сбалансированное, отсортированное дерево, для того чтобы отрабатывать методы вставки, поиска, подсчета размера и глубины дерева

269
Нужен алгоритм java

Нужен алгоритм java

Создаем массив чисел от 1 до 75, с помощью рандома чисел от 1 до 75 начинаем поиск элемента в массивеСовпал - удалили

316