Как определить, заблокирован ли объект (синхронизирован), чтобы не блокировать в Java?
у меня есть процесс A, который содержит таблицу в памяти с набором записей (recordA, recordB и т. д...)
теперь этот процесс может запускать много потоков, которые влияют на записи, и иногда мы можем иметь 2 потока, пытающиеся получить доступ к одной и той же записи - эта ситуация должна быть запрещена. В частности, если запись заблокирована одним потоком, я хочу, чтобы другой поток прервался (я не хочу блокировать или ждать).
в настоящее время я делаю что-то вроде этого:
synchronized(record)
{
performOperation(record);
}
но это вызывает у меня проблемы ... потому что, пока Process1 выполняет операцию, если Process2 входит в него, блокирует/ждет синхронизированного оператора, и когда Process1 завершен, он выполняет операцию. Вместо этого я хочу что-то вроде этого:
if (record is locked)
return;
synchronized(record)
{
performOperation(record);
}
любые подсказки о том, как это может быть достигнуто?
Любая помощь будет высоко ценится.
Спасибо,
7 ответов:
одна вещь, чтобы отметить, что МиГ вы получаете такую информацию, она несвежая. Другими словами, вам могут сказать, что никто не имеет блокировки, но затем, когда вы пытаетесь ее получить, вы блокируете, потому что другой поток вынул блокировку между проверкой и вы пытаетесь ее получить.
Брайан правильно указывает на
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класс делает in. NET. опять же, есть много других вещей, которые мне не нравятся на обеих платформах, когда дело доходит до потоковой передачи - каждый объект потенциально имеет монитор, например!)
посмотри замок объекты, представленные в пакетах параллелизма Java 5.
например
Lock lock = new ReentrantLock() if (lock.tryLock()) { try { // do stuff using the lock... } finally { lock.unlock(); } } ...The ReentrantLock объект по существу делает то же самое, что и традиционный
synchronizedмеханизм, но с большей функциональностью.EDIT: как отметил Джон,
isLocked()метод говорит вам этот момент, и после этого эта информация устарела. Элемент tryLock() метод дайте более надежную работу (обратите внимание, что вы можете использовать это с таймаутом)EDIT #2: Пример теперь включает
tryLock()/unlock()для ясности.
хотя описанный выше подход с использованием объекта блокировки является лучшим способом сделать это, если вы должны быть в состоянии проверить блокировку с помощью монитора, это можно сделать. Однако он поставляется с предупреждением о работоспособности, поскольку этот метод не переносится на не Oracle Java VMs, и он может сломаться в будущих версиях виртуальных машин, поскольку он не является поддерживаемым общедоступным 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 } }мой совет будет использовать этот подход, только если вы не можете выполнить рефакторинг кода, чтобы использовать Ява.утиль.параллельные объекты блокировки для этого и если вы работаете на виртуальной машине Oracle.
Я нашел это, мы можем использовать
Thread.holdsLock(Object obj)чтобы проверить, заблокирован ли объект:возвращает
trueесли и только если текущий поток удерживает блокировку монитора на указанный объект.
хотя ответы на блокировку очень хороши, я думал, что опубликую альтернативу, используя другую структуру данных. По сути, ваши разные темы хотите знать, какие записи будут заблокированы, а какие нет. Один из способов сделать это, чтобы отслеживать заблокированные записи и убедитесь, что структура данных имеет право атомарные операции для добавления записей к запертой набор.
Я буду использовать CopyOnWriteArrayList в качестве примера, потому что это меньше "магии" для иллюстрации. CopyOnWriteArraySet более подходящая структура. Если у вас есть много и много записей, заблокированных в то же время в среднем, то могут быть последствия для производительности с этими реализациями. Правильно синхронизированный хэш-набор тоже будет работать, и блокировки кратки.
в принципе, код использования будет выглядеть так:
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 с синхронизацией может сделать лучше, так как добавить/удалить поиск будет O(1) вместо линейного.
просто другой взгляд на вещи. Просто зависит от того, каковы ваши фактические требования к резьбе. Лично я бы использовал коллекции.synchronizedSet (new HashSet ()) потому что это будет очень быстро... единственный вывод заключается в том, что потоки могут уступить, когда они иначе не имели бы.
еще один обходной путь (в случае, если у вас не было шансов с ответами, приведенными здесь )использует таймауты. т. е. ниже один вернет 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; }
Спасибо за это, это помогло мне решить состязания. Я немного изменил его, чтобы носить как ремень, так и подтяжки.
Итак, вот мое предложение по улучшению принятого ответа:
вы можете убедиться, что вы получаете безопасный доступ к
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()почти в то же время, в результате чего возврат будет потенциально сомнений полно. Я бы хотел сейчас, если я ошибаюсь, Я мог бы быть более осторожным здесь. Но эй! Мой концерт сейчас стабилен: -)..подробнее о моих проблемах развития на моем блог.
Comments