Синхронизировать по значению, а не объекту
Я хочу сделать что-то подобное в Java
public void giveMoney(String userId, int money) {
synchronized (userId) {
Profile p = fetchProfileFromDB(userId);
p.setMoney(p.getMoney() + userId);
saveProfileToDB(p);
}
}
Но, конечно, синхронизация по строке неверна. Какой правильный способ сделать что-то подобное?
Ответы
Ответ 1
Если набор идентификаторов пользователей ограничен, вы можете синхронизировать с интернированной версией String
.
Используйте String.intern()
(у которого есть несколько недостатков) или что-то вроде Guava Interners
, если вам нужно немного больше контролировать интернирование.
Ответ 2
Я думаю, есть несколько вариантов.
Самое простое, что вы можете сопоставить userId
с объектом блокировки на карте потокобезопасности. Другие говорили о интернировании, но я не думаю, что это жизнеспособный вариант.
Однако наиболее распространенным вариантом будет синхронизация на p
(Профиль). Это удобно, если getProfile()
является потокобезопасным, и по его имени я бы предположил, что это возможно.
Ответ 3
В принципе, вы можете синхронизировать любой объект в Java. Это не само по себе "не правильно" для синхронизации на объекте String
; это зависит от того, что именно вы делаете.
Но если userId
является локальной переменной в методе, то это не сработает. Каждый поток, который выполняет этот метод, имеет свою собственную копию переменной (предположительно, ссылаясь на другой объект String
для каждого потока); синхронизация между нитями, конечно, работает только при синхронизации нескольких потоков на одном и том же объекте.
Вам нужно будет сделать объект, который вы синхронизируете, в переменной-члене объекта, который содержит метод, в котором у вас есть блок synchronized
. Если несколько потоков затем вызывают метод на одном и том же объекте, вы достигнете взаимной исключительности.
class Something {
private Object lock = new Object();
public void someMethod() {
synchronized (lock) {
// ...
}
}
}
Вы также можете использовать явные блокировки из пакета java.util.concurrent.locks
, которые могут дать вам больше контроля, если вам это нужно:
class Something {
private Lock lock = new ReentrantLock();
public void someMethod() {
lock.lock();
try {
// ...
} finally {
lock.unlock();
}
}
}
Особенно, если вам нужен эксклюзивный замок для записи, но вы не хотите, чтобы потоки должны были ждать друг друга при чтении, вы можете использовать ReadWriteLock
.
Ответ 4
Вы можете использовать прокси-объект для строки.
Object userIdMutex = new Object();
synchronized (userIdMutex) {
Profile p = getProfile(userId);
p.setMoney(p.getMoney() + p);
saveProfile(p);
}
Используйте этот мьютекс при каждом доступе к userId
.
Ответ 5
Теоретически, поскольку интернированные объекты могут быть GC-ed, его можно синхронизировать по разным объектам (одного и того же значения) в разное время. Взаимная эксклюзивность по-прежнему гарантирована, поскольку невозможно синхронизировать разные объекты одновременно.
Однако, если мы синхронизированы на разных объектах, связь между событиями и данными является сомнительной. Мы должны изучить это, чтобы выяснить. И поскольку в нем участвует GC, к которой не применяется Java-модель памяти, рассуждения могут быть довольно сложными.
Это теоретическое возражение; практически я не думаю, что это вызовет какие-либо проблемы.
Тем не менее, может быть простое, прямое и теоретически правильное решение вашей проблемы. Например Простые блокировки на основе имени Java?
Ответ 6
В соответствии с вашим примером, я предполагаю, что вы хотите получить блокировку для класса профиля, изменить его и затем отпустить блокировку. На мой взгляд, синхронизация не совсем то, что вам нужно. Вам нужен класс, который управляет этими записями, и позволяет блокировать и разблокировать запись, когда в нее нужно внести изменения, а также стиль управления версиями.
Проверьте это: Класс блокировки Java 5
Ответ 7
Как насчет этого:
String userId = ...;
Object userIdLock = new Object();
synchronized (userIdLock) {
Profile p = getProfile(userId);
p.setMoney(p.getMoney() + p);
saveProfile(p);
}
Это простой и, прежде всего, очевидный.