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)?