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