Навіщо використовувати ReentrantLock, якщо можна використовувати синхронізовану (це)?


317

Я намагаюся зрозуміти, що робить замовлення у паралельності настільки важливим, якщо можна використовувати synchronized (this). У наведеному нижче коді я можу зробити:

  1. синхронізували весь метод або синхронізували вразливу область ( synchronized(this){...})
  2. АБО заблокуйте вразливу область коду за допомогою ReentrantLock.

Код:

    private final ReentrantLock lock = new ReentrantLock(); 
    private static List<Integer> ints;

    public Integer getResult(String name) { 
        .
        .
        .
        lock.lock();
        try {
            if (ints.size()==3) {
                ints=null;
                return -9;
            }   

            for (int x=0; x<ints.size(); x++) {
                System.out.println("["+name+"] "+x+"/"+ints.size()+". values >>>>"+ints.get(x));
            }

        } finally {
            lock.unlock();
        } 
        return random;
}

1
До речі, всі внутрішні замки java від природи відсторонені.
Анікет Такур

@pongapundit synchronized(this){synchronized(this){//some code}}не призведе до мертвого блокування. Для внутрішнього блокування, якщо вони отримують монітор на ресурсі, і якщо вони хочуть його знову, вони можуть отримати його без мертвого блокування.
Анікет Такур

Відповіді:


474

ReentrantLock є неструктурованим , в відміну від synchronizedконструкцій - тобто вам не потрібно використовувати блокову структуру для замикання і навіть можете тримати блокування через методу. Приклад:

private ReentrantLock lock;

public void foo() {
  ...
  lock.lock();
  ...
}

public void bar() {
  ...
  lock.unlock();
  ...
}

Такий потік неможливо представити за допомогою одного монітора в synchronizedконструкції.


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

Конструктор цього класу приймає необов'язковий параметр справедливості . Якщо встановлено true, заперечення, блокування сприяє наданню доступу до потоку, який довго чекає. Інакше цей замок не гарантує конкретного замовлення доступу. Програми з використанням справедливих блокувань, доступ до яких здійснюється у багатьох потоках, можуть відображати меншу загальну пропускну здатність (тобто, повільніше; часто набагато повільніше), ніж ті, що використовують налаштування за замовчуванням, але мають менші відхилення в часі, щоб отримати блокування та гарантувати відсутність голоду. Зауважте, що справедливість замків не гарантує справедливості планування ниток. Таким чином, один з багатьох потоків, що використовують справедливий замок, може отримати його кілька разів поспіль, тоді як інші активні потоки не прогресують і в даний час не тримають замок. Також зауважте, що несвоєчаснеtryLockметод не шанує встановлення справедливості. Це вдасться, якщо замок доступний, навіть якщо інші потоки чекають.


ReentrantLock може також бути більш масштабованим , виконуючи набагато кращі результати за вищої конкуренції. Більше про це можна прочитати тут .

Однак ця вимога оспорюється; дивіться наступний коментар:

У тесті блокування повторного замовлення кожен раз створюється новий замок, тому не існує виключного блокування та отримані дані є недійсними. Крім того, посилання IBM не пропонує вихідного коду для базового еталону, тому неможливо визначити, чи тест був проведений правильно.


Коли слід використовувати ReentrantLocks? Відповідно до цієї статті developerWorks ...

Відповідь досить проста - використовуйте її тоді, коли вам потрібно щось, що вона надає, що synchronizedне, як, наприклад, примикання блокування чекає, переривчастий замок чекає, неблоковані блокування, кілька змінних умов або блокування опитування. ReentrantLockтакож має переваги щодо масштабованості, і ви повинні використовувати його, якщо у вас справді виникає ситуація, яка демонструє велику суперечку, але пам’ятайте, що переважна більшість synchronizedблоків навряд чи коли-небудь демонструє суперечки, не кажучи вже про велику суперечку. Я б радив розвиватись із синхронізацією, поки синхронізація не виявиться неадекватною, а не просто припускати, що "продуктивність буде кращою", якщо ви використовуєтеReentrantLock. Пам'ятайте, це передові інструменти для просунутих користувачів. (І справді просунуті користувачі, як правило, віддають перевагу найпростішим інструментам, які вони можуть знайти, поки не переконаються, що прості інструменти є неадекватними.) Як завжди, спочатку зробіть це правильно, а потім переживайте, чи потрібно вам зробити це швидше.


26
Посилання "відомо, що є більш масштабованим" до lycog.com має бути видалено. У тесті блокування повторного замовлення кожен раз створюється новий замок, тому не існує виключного блокування та отримані дані є недійсними. Крім того, посилання IBM не пропонує вихідного коду для базового еталону, тому неможливо визначити, чи тест був проведений правильно. Особисто я просто видалю весь рядок про масштабованість, оскільки вся заява по суті не підтримується.
Dev

2
Я змінив публікацію з урахуванням вашої відповіді.
oldrinb

6
Якщо продуктивність для вас викликає велике занепокоєння, не забувайте шукати спосіб, де вам взагалі НЕ потрібна синхронізація.
mcoolive

2
Спектакль для мене взагалі не має сенсу. Якщо блокування повторного замовлення буде краще, то чому б синхронізація не була б просто реалізована так само, як міжблокове блокування реарантів?
tObi

2
@ user2761895 ReentrantLockPseudoRandomКод у посиланні Lycog використовує абсолютно нові, безперебійні блокування кожного виклику setSeedтаnext
oldrinb

14

ReentrantReadWriteLock- спеціалізований замок, тоді як замок synchronized(this)загального призначення. Вони схожі, але не зовсім однакові.

Ви маєте рацію в тому, що можете використовувати їх synchronized(this)замість, ReentrantReadWriteLockале навпаки не завжди є правдою.

Якщо ви хочете краще зрозуміти, що робить ReentrantReadWriteLockособливим пошук деякої інформації про синхронізацію ниток виробник-споживач.

Загалом ви можете пам’ятати, що синхронізація з цілим методом і загальна мета синхронізації (за допомогою synchronizedключового слова) можна використовувати в більшості програм, не надто замислюючись про семантику синхронізації, але якщо вам потрібно вичавити продуктивність із коду, можливо, вам доведеться вивчити інші більш дрібнозернисті або спеціальні механізми синхронізації.

До речі, використання synchronized(this)- і взагалі блокування за допомогою екземпляра публічного класу - може бути проблематичним, оскільки воно відкриває ваш код потенційним мертвим блокам, оскільки хтось ще не свідомо може спробувати зафіксувати ваш об’єкт десь у програмі.


щоб запобігти потенційним глухим блокам, тому що хтось ще не свідомо може спробувати заблокувати ваш об’єкт десь в програмі, використовуйте приватний екземпляр об’єкта як синхронізацію. Монітор так: public class MyLock { private final Object protectedLongLockingMonitor = new Object(); private long protectedLong = 0L; public void incrementProtectedLong() { synchronized(protectedLongLockingMonitor) { protectedLong++; } } }
sushicutta

9

З сторінки документації Oracle про ReentrantLock :

Блокування взаємного виключення, що повторюється, з такою ж базовою поведінкою та семантикою, як і неявна блокування монітора, до якої можна отримати синхронізовані методи та заяви, але з розширеними можливостями.

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

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

Основні функції ReentrantLock відповідно до цієї статті

  1. Можливість переривання блокування.
  2. Можливість тайм-ауту під час очікування блокування.
  3. Потужність для створення справедливого блокування.
  4. API для отримання списку очікування для блокування.
  5. Гнучкість приміряти замок без блокування.

Ви можете використовувати ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock для подальшого отримання контролю над гранульованим блокуванням на операціях читання та запису.

Погляньте на цю статтю Бенджамена про використання різних типів ReentrantLocks


2

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

private final ReentrantLock lock = new ReentrantLock(true);
//the param true turns on the fairness policy. 

"Політика справедливості" вибирає наступну нитку, яку можна виконати. Він ґрунтується на пріоритеті, часі з моменту останнього запуску, бла-бла

Крім того, синхронізація може блокувати нескінченно, якщо вона не може вийти з блоку. Reentrantlock може встановити тайм-аут.


1

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

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


1

Давайте припустимо, що цей код працює в потоці:

private static ReentrantLock lock = new ReentrantLock();

void accessResource() {
    lock.lock();
    if( checkSomeCondition() ) {
        accessResource();
    }
    lock.unlock();
}

Оскільки потоці належить замок, вона дозволить заблокувати декілька дзвінків (), тому повторно введе блокування. Цього можна досягти за допомогою еталонного підрахунку, тому йому не доведеться купувати замок знову.


0

Слід пам’ятати одне:

ім’я « ReentrantLock » видає неправильне повідомлення про інший механізм блокування про те, що вони не є повторними учасниками. Це не правда.Блокування, придбане за допомогою "синхронізованого", також повторно вводиться в Java.

Ключова відмінність полягає в тому, що "синхронізований" використовує внутрішній замок (такий, який має кожен Об'єкт), тоді як API Lock не робить.


0

Я думаю, що метод wait / notify / notifyAll не належить до класу Object, оскільки він забруднює всі об'єкти методами, які рідко використовуються. Вони мають набагато більше сенсу на спеціальному класі Lock. Отже, з цієї точки зору, можливо, краще скористатися інструментом, який явно призначений для роботи, що знаходиться під рукою - тобто ReentrantLock.

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