Почему создание условий из блокировки, а не с оператором «new»? - PullRequest
0 голосов
/ 01 октября 2019

Если Condition можно использовать отдельно, а создание кода - это просто:

    final ConditionObject newCondition() {
        return new ConditionObject();
    }

, почему он не создан именно таким образом? В классе ArrayBlockingQueue есть код в конструкторе:

    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();

, где notEmpty и notFull являются экземплярами Condition класса.

Ответы [ 3 ]

1 голос
/ 01 октября 2019

Блокировки и условия предоставляют механизм, который ранее (до 1.5) был доступен только через synchronization, wait/notify и пользовательский код условия.

Вот идиоматический пример

synchronized(foo) {           // Lock
    while(!conditionMet)      // Condition
        foo.wait();           // Condition.signalAll();

    // Condition met, do something and notify
    conditionMet = false;
    foo.notifyAll();
}

Может показаться, что условие никак не связано с объектом блокировки, но это гарантирует безопасность потока. Вы не можете написать приведенный выше код без доступа к условию, находящемуся внутри синхронизированного блока. (Вы можете получить доступ к булевому условию из не-поточного кода и все испортить, но вы всегда можете испортить все).

Весь класс ReentrantLock на самом деле является функциональной оболочкой над объектом класса Sync, который отвечает за низкий уровень синхронизации и создание условий. Условия связаны с определенным Lock (или, скорее, Sync), чтобы в основном убедиться, что они поточно-ориентированы. Поскольку в Javadoc для Condition указано

Поскольку доступ к этой информации общего состояния происходит в разных потоках, она должна быть защищена, поэтому блокировка некоторой формы связана с условием.

1 голос
/ 01 октября 2019

Как уже упоминал Slaw, создание отдельного нового условия просто не имеет смысла, поскольку оно потеряет ассоциацию с исходным объектом Lock, и это будет похоже на создание нового условия из другого Lock (потому что java.util.concurrent.locks.Condition не может быть создан отдельно).

Мы можем обратиться к нижеприведенным 2 примерам, чтобы понять больше:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionTest {

    final ReentrantLock lock = new ReentrantLock();
    final Condition notEmpty = lock.newCondition();
    final Condition notFull =  lock.newCondition();

    String message;
    boolean ready;
    boolean isCompleted;

    public void consume() {

        ReentrantLock lock = this.lock;
        lock.lock();
        try { 

            while(!ready)
                notEmpty.await();

            System.out.println("Received message: " + message);
            ready = !ready; // reverse the "ready" state
            notFull.signal();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally { 
            lock.unlock();
        }

    }

    public void publish(String message) {
        ReentrantLock lock = this.lock;
        lock.lock();
        try { 
            while(ready)
                notFull.await();

            System.out.println("Adding message");
            this.message = message;
            ready = !ready; // reverse the "ready" state

            notEmpty.signal();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally { 
            lock.unlock();
        }       
    }


    public static void main(String[] args) {

        ConditionTest ct = new ConditionTest();

        Thread msgProducerThread = new Thread(() -> {
            List<String> messages = new ArrayList<String>();
            messages.add("Hello!");
            messages.add("here we get a message");
            messages.add("then we get another message");        

            messages.forEach(m -> ct.publish(m));

            ct.isCompleted = true;
        });

        Thread msgConsumerThread = new Thread(() -> {
            while (!ct.isCompleted)
                ct.consume();
        });

        msgProducerThread.start();
        msgConsumerThread.start();

    }   
}

Тогда мы получим результат ниже, что означает, что Lock and Condition функционируют правильно:

Добавление сообщения

Полученное сообщение: привет

Добавление сообщения

Полученное сообщение: текущий проект завершен

Добавление сообщения

Полученное сообщение: вот оценка для нового проекта

Однако, если мы попытаемся использовать отдельное Условие, заменив исходный код:

Condition notEmpty = lock.newCondition();
Condition notFull =  lock.newCondition();

на:

ReentrantLock lock2 = new ReentrantLock(fair);
ReentrantLock lock3 = new ReentrantLock(fair);
Condition notEmpty = lock2.newCondition();
Condition notFull =  lock3.newCondition();

Затем он получит:

Adding message
Exception in thread "Thread-0" Received message: Hello!
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
    at ConditionTest.publish(ConditionTest.java:54)
    at ConditionTest.lambda$0(ConditionTest.java:75)
    at java.lang.Thread.run(Thread.java:745)
java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
    at ConditionTest.consume(ConditionTest.java:33)
    at ConditionTest.lambda$1(ConditionTest.java:83)
    at java.lang.Thread.run(Thread.java:745)

Это означает, что не существует соответствующей связи между Условием и Блокировкой, что приводит к созданию исключения IllegalMonitorStateException.

1 голос
/ 01 октября 2019

Из документации Condition:

Condition исключает Object методы мониторинга (wait, notify и notifyAll)в отдельные объекты, чтобы получить эффект наличия нескольких наборов ожидания на объект, комбинируя их с использованием произвольных Lock реализаций. Где Lock заменяет использование synchronized методов и операторов, Condition заменяет использование Object методов монитора.

Условия (также известные как очереди условий или переменные условия) предоставляет способ для одного потока приостановить выполнение («подождать») до тех пор, пока другой поток не уведомит о том, что какое-то условие состояния теперь может выполнятьсяПоскольку доступ к этой информации общего состояния происходит в разных потоках, она должна быть защищена, поэтому блокировка некоторой формы связана с условием. Ключевое свойство, которое обеспечивает ожидание условия, заключается в том, что оно атомарно снимает связанную блокировку и приостанавливает текущий поток, так же как Object.wait.

A Condition экземпляр по своей природе связан сзамок. Чтобы получить экземпляр Condition для конкретного экземпляра Lock, используйте его метод newCondition().

Как объяснено, Condition экземпляр должен быть связан с Lock экземпляром 1 . Наличие Lock в качестве фабрики для создания экземпляров Condition имеет смысл с учетом этого, поскольку подразумевает взаимосвязь между ними. Другой способ, которым это соотношение могло бы быть реализовано, - дать Condition конструктор, который принимает экземпляр Lock, но , поскольку Condition также является интерфейсом , который не может объявлять конструкторы. Я также считаю, что метод фабрики без аргументов в этом случае более удобен для пользователя.

Примечание: Если это еще не ясно, ReentrantLockкласс является реализацией интерфейса Lock, а класс ConditionObject является реализацией интерфейса Condition.

Другая проблема при попытке использовать ConditionObject напрямую заключается в том, что этовнутренний класс (т. е. нестатический вложенный класс) AbstractQueuedSynchronizer 2 . Это означает, что вам понадобится экземпляр последнего класса, чтобы создать экземпляр первого класса. Однако реализация AbstractQueuedSynchronizer, используемая ReentrantLock, представляет собой подробность реализации и не доступна широкой публике. Другими словами, у вас нет возможности вызвать конструктор ConditionObject, что означает, что единственный способ создания экземпляра - это newCondition().

Напомним, что существует как минимум три причины, по которым метод фабрикииспользуется для создания Condition объектов:

  1. Это делает связь между Lock и Condition ясной.
  2. Поскольку интерфейсы Lock и Condition являютсянужен способ связать Condition с Lock, не зная о реализациях. В противном случае было бы невозможно «программировать на интерфейс» .
  3. Поскольку ConditionObject является внутренним классом, его нельзя создать напрямую - по крайней мере, не с помощью кода, который не 'у него нет доступа к экземпляру включающего класса.

1. Методы Condition имеют смысл только в контексте владения Lock. Точно так же, как поток должен быть synchronized для объекта, прежде чем он сможет законно вызывать методы монитора этого объекта (например, wait / notify), поток должен владеть ассоциированным Lock, прежде чем он сможет законно вызывать методы Condition(т.е. ожидание / сигнал).

2. Также есть AbstractQueuedLongSynchronizer, который объявляет свой собственный ConditionObject внутренний класс. Хотя имя класса совпадает с именем, объявленным AbstractQueuedSynchronizer, оба фактически являются отдельными классами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...