Java мьютекс и состояние объекта гибернации

java #hibernate #junit #mutex

#java #переход в режим гибернации #junit #мьютекс

Вопрос:

Тестовая реализация

Я запускаю этот фрагмент кода в четырех потоках параллельно:

 private volatile static boolean mutex = false;

private class Worker implements Runnable {

    @Override
    public void run() {
        Session session = sessionFactory.openSession();
        for (int i = 0; i < UPDATE_COUNT; i  ) {
            plusOne(session, Thread.currentThread().getId());
        }
        session.close();
    }

    private synchronized void plusOne(Session session, long threadId) {
        while (mutex) {
            Thread.yield();
        }
        mutex = true;
        System.out.printf("thread-%d: mutex lock, begin, updaten", threadId);
        session.beginTransaction();
        MyEntity myEntity =
            (MyEntity) session.createQuery("from MyEntity").list().get(0);
        int oldValue = myEntity.getMyColumn();
        int newValue = oldValue   1;
        myEntity.setMyColumn(newValue);
        System.out.printf("thread-%d: %d   1 = %dn",
            threadId, oldValue, newValue);
        System.out.printf("thread-%d: commit, mutex releasen", threadId);
        session.getTransaction().commit();
        mutex = false;
    }

}
 

Вывод

Вот пример вывода:

 ...
thread-17: mutex lock, begin, update
thread-17: 1   1 = 2
thread-17: commit, mutex release
thread-17: mutex lock, begin, update
thread-18: mutex lock, begin, update
thread-19: mutex lock, begin, update
thread-17: 2   1 = 3
thread-17: commit, mutex release
thread-18: 2   1 = 3
thread-18: commit, mutex release
...
 

Выходные данные меняются из-за планирования потоков.

Вопрос № 1: Java мьютекс (РЕДАКТИРОВАТЬ: решен)

 thread-17: mutex lock, begin, update
thread-18: mutex lock, begin, update
...
thread-17: commit, mutex release
 

Как можно thread-18 достичь этой линии между двумя thread-17 строками в
синхронизированном методе, который использует логическую переменную mutex для принятия решения о том, должен ли
поток быть передан или нет?

Можете ли вы предоставить нам правильный способ реализации мьютекса?

РЕДАКТИРОВАТЬ: см. Сообщение @Thomas ниже. Метод plusOne синхронизируется только внутри рабочего объекта. Его предложение synchronized(Worker.class) { ... } решает проблему.

Вопрос № 2: значение атрибута объекта после фиксации (РЕДАКТИРОВАНИЕ: решено)

 thread-17: 2   1 = 3
thread-17: commit, mutex release
thread-18: 2   1 = 3
thread-18: commit, mutex release
 

How can oldValue be 2 on thread-18 even though thread-17 has committed
the value 3 ? Maybe it’s because the Java mutex fails (Question #1) and both
threads can enter parallel the line int oldValue = myEntity.getMyColumn()
where the old value is read?

РЕДАКТИРОВАТЬ: эта проблема была вызвана ошибкой Java mutex, описанной выше.

Вопрос № 3: Состояние объекта после того, как рабочие потоки готовы (РЕДАКТИРОВАТЬ: решено)

Так выполняются потоки и утверждается значение:

 // Create one db row.
Session session = sessionFactory.openSession();
session.beginTransaction();
if (session.createQuery("from MyEntity").list().isEmpty()) {
    MyEntity myEntity = new MyEntity();
    myEntity.setMyColumn(0);
    session.save(myEntity);
}
session.getTransaction().commit();
// close here //

// Update the row simultaneously with multiple workers.
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < WORKER_COUNT; i  ) {
    threads.add(new Thread(new Worker()));
}
threads.stream().forEach(Thread::start);
for (Thread thread : threads) thread.join();

// Check the result.
// open here //
session.beginTransaction();
MyEntity myEntity =
    (MyEntity) session.createQuery("from MyEntity").list().get(0);
assertEquals(WORKER_COUNT * UPDATE_COUNT,
    myEntity.getMyColumn().intValue());
session.getTransaction().commit();
session.close();
 

После устранения проблем в вопросах # 1 и # 2 утверждение по-прежнему терпит неудачу: expected:<20> but was:<0>

Почему это?

РЕДАКТИРОВАТЬ: я решил эту проблему, закрыв начальный сеанс после создания объекта и открыв новый для утверждения. Смотрите блок кода выше. Заменить

  • // close here // с session.close();
  • // open here // с session = sessionFactory.openSession();

Окружающая среда

  • Debian Bullseye
  • OpenJDK 11.0.12 7-post-Debian-2
  • Переход в режим гибернации 5.6.0
  • JUnit 4.13.2

Попробуйте сами

Смотрите Этот репозиторий для простого способа воспроизведения проблемы.

Комментарии:

1. Проблема с вашим plusOne() методом заключается в том, что он по существу не синхронизирован, т. Е. Синхронизация происходит на Worker экземпляре и, следовательно, не является общедоступной. Вам нужно синхронизировать что-то еще, например, добавить synchronized(Worker.class) { ... } insicde в метод.

2. Спасибо @Thomas. Это решило проблему. Можете ли вы проверить новый вопрос (# 3)?