Как определить, заблокирован ли объект (синхронизирован), чтобы он не блокировался на Java?
У меня есть процесс A, содержащий таблицу в памяти с набором записей (recordA, recordB и т.д.)
Теперь этот процесс может запускать много потоков, которые влияют на записи, и иногда у нас может быть 2 потока, пытающихся получить доступ к одной и той же записи - эта ситуация должна быть отклонена. В частности, если запись LOCKED одним потоком, я хочу, чтобы другой поток прерывался (я не хочу БЛОКИРОВАТЬ или ЖДАТЬ).
В настоящее время я делаю что-то вроде этого:
synchronized(record)
{
performOperation(record);
}
Но это вызывает у меня проблемы... потому что, пока Process1 выполняет операцию, если Process2 входит в нее, блокирует/ждет синхронизированный оператор и когда Process1 завершен, он выполняет операцию. Вместо этого я хочу что-то вроде этого:
if (record is locked)
return;
synchronized(record)
{
performOperation(record);
}
Любые подсказки о том, как это можно сделать?
Любая помощь приветствуется.
Спасибо,
Ответы
Ответ 1
Следует отметить, что в тот момент, когда вы получаете такую информацию, она устарела. Другими словами, вам может быть сказано, что никто не имеет блокировки, но затем, когда вы пытаетесь его приобрести, вы блокируете, потому что другой поток вытащил блокировку между проверкой и попытался ее приобрести.
Брайан прав, указывая на Lock
, но я думаю, что вы действительно хотите его tryLock
:
Lock lock = new ReentrantLock();
......
if (lock.tryLock())
{
// Got the lock
try
{
// Process record
}
finally
{
// Make sure to unlock so that we don't cause a deadlock
lock.unlock();
}
}
else
{
// Someone else had the lock, abort
}
Вы также можете вызвать tryLock
с ожидаемым количеством времени, чтобы вы могли попытаться приобрести его на десятую часть секунды, а затем прервать, если вы не можете его получить (например).
(Мне жаль, что Java API не имеет - насколько мне известно, - обеспечивает ту же функциональность для "встроенной" блокировки, что и класс Monitor
в .NET. опять же, есть много других вещей, которые мне не нравятся на обеих платформах, когда дело доходит до потоковой передачи - каждый объект, потенциально имеющий монитор, например!)
Ответ 2
Взгляните на Lock объекты, представленные в пакетах Java 5 concurrency.
например.
Lock lock = new ReentrantLock()
if (lock.tryLock()) {
try {
// do stuff using the lock...
}
finally {
lock.unlock();
}
}
...
Объект ReentrantLock по сути делает то же самое, что и традиционный механизм synchronized
, но с большей функциональностью.
EDIT: Как отметил Джон, метод isLocked()
сообщает об этом в тот момент, и после этого эта информация устарела. Метод tryLock() даст более надежную работу (обратите внимание, что вы также можете использовать это с таймаутом)
ИЗМЕНИТЬ №2: Пример теперь включает tryLock()/unlock()
для ясности.
Ответ 3
В то время как вышеупомянутый подход с использованием объекта Lock является наилучшим способом его выполнения, если вы должны иметь возможность проверить блокировку с помощью монитора, это можно сделать. Тем не менее, он поставляется с предупреждением о состоянии здоровья, поскольку этот метод не переносится на неинвазивные Java-виртуальные машины, и он может разорваться в будущих версиях VM, поскольку он не поддерживает общедоступный API.
Вот как это сделать:
private static sun.misc.Unsafe getUnsafe() {
try {
Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void doSomething() {
Object record = new Object();
sun.misc.Unsafe unsafe = getUnsafe();
if (unsafe.tryMonitorEnter(record)) {
try {
// record is locked - perform operations on it
} finally {
unsafe.monitorExit(record);
}
} else {
// could not lock record
}
}
Моим советом было бы использовать этот подход, только если вы не можете реорганизовать свой код для использования объектов java.util.concurrent Lock для этого и если вы работаете на виртуальной машине Oracle.
Ответ 4
Я нашел это, мы можем использовать Thread.holdsLock(Object obj)
чтобы проверить, заблокирован ли объект:
Возвращает true
тогда и только тогда, когда текущий поток удерживает блокировку монитора для указанного объекта.
Обратите внимание, что Thread.holdsLock()
возвращает false
если блокировка чем-то удерживается и вызывающий поток не является потоком, который удерживает блокировку.
Ответ 5
В то время как ответы "Блокировка" очень хорошие, я думал, что отправлю альтернативу, используя другую структуру данных. По сути, ваши различные потоки хотят знать, какие записи заблокированы, а какие нет. Один из способов сделать это - отслеживать заблокированные записи и убедиться, что структура данных имеет правильные атомные операции для добавления записей в заблокированный набор.
Я буду использовать экземпляр CopyOnWriteArrayList в качестве примера, потому что он менее "волшебный" для иллюстрации. CopyOnWriteArraySet - более подходящая структура. Если в среднем у вас есть много и много записей в одно и то же время, то с этими реализациями могут возникнуть последствия для производительности. Правильно синхронизированный HashSet будет работать и блокировки будут краткими.
В принципе, код использования будет выглядеть следующим образом:
CopyOnWriteArrayList<Record> lockedRecords = ....
...
if (!lockedRecords.addIfAbsent(record))
return; // didn't get the lock, record is already locked
try {
// Do the record stuff
}
finally {
lockedRecords.remove(record);
}
Это не позволяет вам управлять блокировкой на запись и предоставляет одно место, чтобы по какой-то причине необходимо было очистить все блокировки. С другой стороны, если у вас когда-либо больше, чем несколько записей, то реальный HashSet с синхронизацией может улучшиться, так как для добавления/удаления look-ups будет O (1) вместо линейного.
Просто другой способ взглянуть на вещи. Просто зависит от ваших фактических требований к потоку. Лично я бы использовал Collections.synchronizedSet(новый HashSet()), потому что он будет очень быстрым... единственная импликация заключается в том, что потоки могут появляться, когда они иначе не имели бы.
Ответ 6
Другим обходным решением является (в случае, если у вас не было шансов с ответами, приведенными здесь) используется тайм-ауты. то есть ниже один возвращает значение null после 1 секунды висит:
ExecutorService executor = Executors.newSingleThreadExecutor();
//create a callable for the thread
Future<String> futureTask = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return myObject.getSomething();
}
});
try {
return futureTask.get(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
//object is already locked check exception type
return null;
}
Ответ 7
Спасибо за это, это помогло мне решить условия гонки. Я немного поменял его, чтобы носить как пояс, так и подтяжки.
Итак, вот мое предложение для УЛУЧШЕНИЯ принятого ответа:
Вы можете обеспечить безопасный доступ к методу tryLock()
, выполнив что-то вроде этого:
Lock localLock = new ReentrantLock();
private void threadSafeCall() {
boolean isUnlocked = false;
synchronized(localLock) {
isUnlocked = localLock.tryLock();
}
if (isUnlocked) {
try {
rawCall();
}
finally {
localLock.unlock();
}
} else {
LOGGER.log(Level.INFO, "THANKS! - SAVED FROM DOUBLE CALL!");
}
}
Это позволит избежать ситуации, когда вы можете получить два вызова tryLock()
почти в одно и то же время, в результате чего возвращение будет потенциально сомнительным. Я бы хотел, если бы я ошибся, я мог бы быть здесь. Но эй! Теперь мой концерт стабилен: -)..
Подробнее о моих проблемах разработки читайте в Блог.