Подтвердить что ты не робот

Как определить, заблокирован ли объект (синхронизирован), чтобы он не блокировался на Java?

У меня есть процесс A, содержащий таблицу в памяти с набором записей (recordA, recordB и т.д.)

Теперь этот процесс может запускать много потоков, которые влияют на записи, и иногда у нас может быть 2 потока, пытающихся получить доступ к одной и той же записи - эта ситуация должна быть отклонена. В частности, если запись LOCKED одним потоком, я хочу, чтобы другой поток прерывался (я не хочу БЛОКИРОВАТЬ или ЖДАТЬ).

В настоящее время я делаю что-то вроде этого:

synchronized(record)
{
performOperation(record);
}

Но это вызывает у меня проблемы... потому что, пока Process1 выполняет операцию, если Process2 входит в нее, блокирует/ждет синхронизированный оператор и когда Process1 завершен, он выполняет операцию. Вместо этого я хочу что-то вроде этого:

if (record is locked)
   return;

synchronized(record)
{
performOperation(record);
}

Любые подсказки о том, как это можно сделать? Любая помощь приветствуется. Спасибо,

4b9b3361

Ответ 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() почти в одно и то же время, в результате чего возвращение будет потенциально сомнительным. Я бы хотел, если бы я ошибся, я мог бы быть здесь. Но эй! Теперь мой концерт стабилен: -)..

Подробнее о моих проблемах разработки читайте в Блог.