Как работают lock объекты?

335
29 марта 2017, 15:44

Я пытаюсь разобраться с логикой лок-объектов на нескольких примерах, речь всегда пойдет об одном экземпляре MyClass:

Пример 1:

Если поток один зайдет в метод a(), то поток два в метод b(), уже войти не сможет, пока первый поток из метода a(), не выйдет? Потому что объект монитор у нас один и тот-же? Или не правильно?

MyClass {
    final Object obj = new Object();
    void a() {
        synchronized(obj) {
            // do something...
        }
    }
    void b() {
        synchronized(obj) {
            // do something...
        }
    }
}

Пример 2:

Если поток один зайдет в метод a(), то поток два в метод b(), войти сможет. Потому что объекты мониторов у нас разные. Или не правильно?

MyClass {
    final Object obj1 = new Object();
    final Object obj2 = new Object();
    void a() {
        synchronized(obj1) {
            // do something...
        }
    }
    void b() {
        synchronized(obj2) {
            // do something...
        }
    }
}

Пример 3:

Если поток один зайдет в метод a(), то поток два в метод b(), уже войти не сможет, пока первый поток из метода a(), не выйдет? Потому что объект монитор у нас один и тот-же? Как в примере один.

Значит ли это, что я могу спокойно обращаться к методам read и write, и быть уверенным что потери данных, из-за гонки, или кэширования не произойдет?

MyClass {
    final Map<String, String> map = new HashMap<>();
    void a() {
    String read(final String key, final String value) {
        synchronized(map) {
            this.map.put(key, value);
        }
    }
    String write(final String key) {
        synchronized(map) {
            return this.map.get(key);
        }
    }
}

Пример 4:

Будет ли являться такой код потокобезопасным в ситуации, если обращаться к методам read и write, из разных потоков? Почему? Что может произойти?

MyClass {
    volatile Map<String, String> map = new HashMap<>();
    final Object readLock = new Object();
    final Object writeLock = new Object();
    String read(final String key, final String value) {
        synchronized(readLock) {
            this.map.put(key, value);
        }
    }
    String write(final String key) {
        synchronized(writeLock) {
            return this.map.get(key);
        }
    }
}

Спасибо.

Answer 1

Disclaimer: формально оба потока смогут зайти в любые методы во всех примерах - они могут остановиться на synchronized-блоке, который расположен внутри метода. Понятно, что интересовала именно синхронизация, поэтому дальше идет речь исключительно про блокировку одного потока другим в критической секции.

  1. Если поток один зайдет в метод a(), то поток два в метод b() уже войти не сможет, пока первый поток из метода a() не выйдет? Потому что объект монитор у нас один и тот же?

Да, все верно.

  1. Если поток один зайдет в метод a(), то поток два в метод b() войти сможет. Потому что объекты мониторов у нас разные.

Тоже все верно.

  1. Если поток один зайдет в метод a(), то поток два в метод b() уже войти не сможет, пока первый поток из метода a() не выйдет?

Да, но здесь нужно быть очень аккуратным (и, возможно, я даже ошибусь с написанием этого ответа). Порядок операций в Java определяет Java Memory Model, из которой проистекает отношение happens-before, которым и нужно оперировать для определения порядка видимости изменений: happens-before между операциями X и Y гарантирует, что результат операции Х будет виден последующей операции Y (X happens before Y -> "X происходит до Y"). Сам по себе Map/HashMap не определяет отношение hb, но у Java есть следующие обязательные отношения hb:

  • Каждое последующее выражение внутри одного потока имеет отношение happens-before к предыдущему
  • Выход из synchronized-блока и последующий вход имеют отношение happens-before
  • Для hb(a, b) и hb(b, c) действует отношение hb(a, c)

Насколько понимаю, данный подход безопасен, но лучше сразу завернуть целиком метод в synchronized и/или взять ConcurrentHashMap - если интересно, почему я ттак беспокоюсь, можно почитать легко находимые статьи про double checked locking, в которых описывается похожий случай, который не работает. К сожалению, у меня сейчас нет времени, чтобы ответить на этот вопрос твердо и точно, общим подходом в таких случаях является better safe than sorry и жертва теоретической производительности в пользу гарантий.

  1. Будет ли являться такой код потокобезопасным в ситуации, если обращаться к методам read и write из разных потоков?

Нет, потому что он определяет отдельно порядок чтений и отдельно порядок записи. Здесь нет отношений hb от записи к чтению, поэтому поток совершенно точно вправе увидеть любой мусор.

Позволю себе минуту брюзжания:

тот-же

Тот же

Или не правильно

неправильно

поток два в метод b(), уже войти не сможет

первый поток из метода a(), не выйдет

поток два в метод b(), войти сможет

поток два в метод b(), уже войти не сможет

поток из метода a(), не выйдет

обращаться к методам read и write, из разных потоков

Ни одна из этих запятых не нужна, здесь практически везде подлежащее-сказуемое, и между ними запятые (без дополнительных оборотов и прочих усложнений) не ставятся

потери данных, из-за гонки, или кэширования

здесь другой случай, но тоже не нужны

Answer 2
  1. Да
  2. Да
  3. Да
  4. Нет, не будет, объекты для блокировки разные, как в примере 2. Произойдет что-угодно, так как хеш-таблица может прийти в некорректное состояние. Если чуть сложнее, то в Java есть модель памяти, которая может точно сказать какое поведение будет. Но состояние таблицы в любом случае может быть нарушено.

Если хотите, быстрые read/write блокировки, то можно использовать ReentrantReadWriteLock.

READ ALSO
Каким образом необходимо внедрить Hibernate Session Factory в Spring MVC?

Каким образом необходимо внедрить Hibernate Session Factory в Spring MVC?

Необходимо реализовать CRUD операции с БД MySQLИспользую Repository для доступа у данным

370
Хранение много-тайловых объектов в тайл-карте

Хранение много-тайловых объектов в тайл-карте

Здравствуйте! Мне нобходимо найти способ хранить тайлы, с размерами больше 1x1 в стандартной тайл-карте (tilemap, как по русски то?)

362
Настройка шрифта в IntelliJ IDEA

Настройка шрифта в IntelliJ IDEA

Подскажите пожалуйста, как "утолстить" шрифт в IntelliJ IDEА? Просто в Windows он настолько тонок, что смотреть неприятно

528