#java #multithreading #kotlin #telegram #synchronized
Вопрос:
Я использую следующий метод в коде моего телеграмм-бота:
fun save(tUser: org.telegram.telegrambots.meta.api.objects.User): User {
val userId = tUser.id;
synchronized(this) {
val user = userRepository.findById(userId).orElse(User(userId))
user.apply {
username = tUser.userName
firstName = tUser.firstName
lastName = tUser.lastName
languageCode = tUser.languageCode
lastMessageReceived = Instant.now()
}
return userRepository.save(user)
}
}
использование синхронизации блоков вызвано следующей причиной — сервер Telegram отправляет сообщения в нескольких потоках, поэтому два запроса от одного и того же пользователя могут поступать одновременно.
Таким образом, поиск и вставка в базу данных пользователей выполняется строго в одном потоке. Но нет необходимости синхронизировать весь объект контроллера(это) (обработка разных пользователей может идти параллельно), т. Е. мне нужно синхронизировать, скажем, по ключу ConcurrentHashMap, например, так:
private val users: ConcurrentHashMap<Long, Long> = ConcurrentHashMap()
fun save(tUser: org.telegram.telegrambots.meta.api.objects.User): User {
val userId = users.putIfAbsent(tUser.id, tUser.id)!!
synchronized(userId) {
val user = userRepository.findById(userId).orElse(User(userId))
user.apply {
username = tUser.userName
firstName = tUser.firstName
lastName = tUser.lastName
languageCode = tUser.languageCode
lastMessageReceived = Instant.now()
}
return userRepository.save(user)
}
}
было бы это правильно?
Здесь я вижу еще одну проблему — потоки замедляются на ConcurrentHashMap, хотя и не так сильно, как в первом случае, когда доступ к БД осуществляется внутри синхронизированного блока(это)
Комментарии:
1. Я считаю, что синхронизация на
Long
самом деле не очень хорошая идея. Вы можете использовать ту же технику, но хранитьReentrantLock
объекты на карте (или простоAny()
) и синхронизировать их. Недостатком является то, что карта будет расти со временем, и удалить неиспользуемые блокировки нетривиально.2. Какова фактическая реализация
userRepository
? Любая разумная реляционная БД не будет иметь проблем с одновременным выбором и обеспечит точный контроль над тем, что происходит при вставках и обновлениях3. @DavidSoroko, я использую postgres. Если я не использую блокировку: Может возникнуть ситуация, когда один и тот же пользователь (отсутствующий в базе данных) входит в два потока. И тогда первый запрос будет выполнен успешно, но второй завершится сбоем с нарушением первичного ключа
4. Синхронизация на стороне сервера не решит проблему, если вы запустите несколько экземпляров. Вы могли бы рассмотреть вопрос О КОНФЛИКТЕ «НИЧЕГО не ДЕЛАЙ» в качестве альтернативы: postgresql.org/docs/9.5/sql-insert.html
5. или просто проигнорируйте/проглотите уникальное исключение нарушения ограничений при вставке в ваш код. Это почти одно и то же.