Як визначити, чи заблоковано (синхронізовано) об’єкт, щоб не блокувати його в Java?


81

У мене є процес A, який містить таблицю в пам'яті з набором записів (recordA, recordB тощо ...)

Тепер цей процес може запустити багато потоків, які впливають на записи, а іноді ми можемо мати 2 потоки, які намагаються отримати доступ до одного і того ж запису - цю ситуацію потрібно заперечувати. Зокрема, якщо запис БЛОКУЄТЬСЯ одним потоком, я хочу, щоб інший потік перервався (я не хочу БЛОКУВАТИ або ЗАЧЕКАТИ).

В даний час я роблю щось подібне:

synchronized(record)
{
performOperation(record);
}

Але це викликає у мене проблеми ... тому що, поки Process1 виконує операцію, якщо Process2 надходить, він блокує / чекає синхронізований оператор, і коли Process1 закінчується, він виконує операцію. Натомість я хочу щось подібне:

if (record is locked)
   return;

synchronized(record)
{
performOperation(record);
}

Будь-які підказки про те, як цього можна досягти? Будь-яка допомога буде дуже вдячна. Дякую,

Відповіді:


87

Одна річ , щоб відзначити , що миттєва ви отримуєте таку інформацію, то несвіжий. Іншими словами, вам могли сказати, що ні в кого немає блокування, але тоді, коли ви намагаєтесь його отримати, ви блокуєте, оскільки інший потік витягнув замок між чеком і ви намагаєтесь його отримати.

Брайан має рацію, вказуючи на це 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. Знову ж таки, існує безліч інші речі, які мені не подобаються на обох платформах, коли справа доходить до різьбових потоків - наприклад, кожен об'єкт, який потенційно може мати монітор!)


Так. Це хороший момент. Я сприйняв приклад коду буквально, тоді як вищезазначене, безумовно, є більш надійною реалізацією
Брайан Егню

5
Але як я можу використовувати блокування для запису? На даний момент записи зберігаються в HashTable записів ... тож мені потрібен відповідний Hashtable замків? Я намагаюся забезпечити, щоб у мене була максимально можлива паралельність, тому, якщо процес хоче отримати доступ до recordC, який повинен бути нормальним (якщо заблоковано лише recordB) - я використовую глобальний LOCK, то це по суті те саме, що і блокування всього хеш-таблиці. ... що має якийсь сенс?
Shaitan00

@ Shaitan00: Найпростішим способом було б заблокувати запис. В основному вам потрібен один замок, пов’язаний із кожним записом - тому помістіть його в об’єкт.
Джон Скіт,

Очевидно :) Я припускаю, що мені не потрібно керувати розблокуванням? Значення, коли воно виходить із {} .tryLock (), воно буде автоматично і негайно розблоковано правильно?
Shaitan00

1
Вам потрібно керувати розблокуванням. Дивіться підручник, на який я посилався у своїй відповіді, та метод unlock () у блоці нарешті {}
Брайан Егню

24

Погляньте на об'єкти Lock, представлені в пакетах одночасності Java 5.

напр

Lock lock = new ReentrantLock()
if (lock.tryLock()) {
   try {
      // do stuff using the lock...
   }
   finally {
      lock.unlock();
   }
}
   ...

Об'єкт ReentrantLock по суті робить те саме, що і традиційний synchronizedмеханізм, але з більшою функціональністю.

РЕДАГУВАТИ: Як зауважив Джон, isLocked()метод повідомляє вам у цей момент , а після цього інформація застаріла. Метод tryLock () забезпечить більш надійну роботу (зверніть увагу, ви також можете використовувати це з тайм-аутом)

EDIT # 2: Приклад тепер включає tryLock()/unlock()для наочності.


11

Я знайшов це, ми можемо використовувати, Thread.holdsLock(Object obj)щоб перевірити, чи заблоковано об’єкт:

Повертає trueтоді і лише тоді, коли поточний потік утримує блокування монітора для вказаного об'єкта.

Зверніть увагу, що Thread.holdsLock()повертається, falseякщо замок чимось утримується, а викличний потік не є потоком, який утримує замок.


8

Хоча вищевказаний підхід із використанням об’єкта Lock є найкращим способом це зробити, якщо вам потрібно мати можливість перевірити наявність блокування за допомогою монітора, це можна зробити. Однак він постачається із попередженням про стан здоров'я, оскільки техніка не є портативною для віртуальних машин, що не належать до Oracle Java, і вона може зламатись у майбутніх версіях віртуальних машин, оскільки вона не підтримується загальнодоступним 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.


12
@alestanis, я не згоден. Ось я читаю ці відповіді сьогодні і радію всім відповідям / коментарям, незалежно від того, коли вони даються.
Том

@Tom Ці відповіді позначені системою SO, ось чому я залишив повідомлення. Ви побачите, коли почнете переглядати :)
alestanis

2
@alestanis, я вважаю, що це шкода - я часто бачу старі запитання, на які є прийняті відповіді, але які також мають нові, кращі відповіді або через те, що змінилися технології, або тому, що прийнята відповідь насправді була не зовсім правильною.
Том

1
Окрім того, що ця тема стара і має хорошу відповідь, ця відповідь тут не є хорошою. Якщо ви дійсно хочете заблокувати за допомогою монітора, ви можете використовувати Thread.holdsLock (об'єкт).
Тобіас

3

Незважаючи на те, що відповіді на 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класі, я думаю.
bvdb

1

Іншим обхідним шляхом є (якщо у вас не було шансів з наведеними тут відповідями) - використання тайм-аутів. тобто нижче одиниці поверне нуль після 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;
        }

-2

Дякую за це, це допомогло мені вирішити умови перегонів. Я трохи змінив його, щоб носити як ремінь, так і підтяжки.

Тож ось моя пропозиція ВДОСКОНАЛЕННЯ прийнятої відповіді:

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

Детальніше про мої проблеми з розробкою читайте в моєму блозі .


1
використання синхронізованого на об'єкті блокування дуже і дуже погано. Вся суть блокування полягає у тому, щоб уникнути синхронізації, тому ви можете зробити тайм-аут або швидко повернутися, коли не можете отримати замок.
Аякс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.