Подождать/уведомить блокировку

У меня есть очередь с некоторым механизмом блокировки в методах «Добавить» и «Получить», где первый поток добавляет данные, а второй поток получает данные.

public synchronized MyObj getData() {               
    synchronized (myLock) {
        synchronized (this) {
            if (isEmpty()) {                
                wait(0);                    
            }
        }       


        return getData();           
    }
}

public synchronized void addData(MyObj data) {
    if (!isFull()) {
        putData(data);
        synchronized (this) {
            notify();
        }
    }
}

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

Теперь я хочу добавить еще одну «блокировку», когда очередь заполнена, и кто-то пытается добавить в нее больше данных:

public synchronized MyObj getData() {               
    synchronized (myLock) {
        synchronized (this) {
            if (isEmpty()) {                
                wait(0);                    
            }
        }       

        synchronized (this) {
            notify();
        }
        return getData();           
    }
}

public synchronized void addData(MyObj data) {
    synchronized (myLock) {
        synchronized (this) {
            if (isFull()) {
                wait(0);
            }
        }
    }

    synchronized (this) {
        notify();
        }
        PutData(data);
}

Результат не тот, что я ожидаю, я думаю, что я получаю мертвую блокировку, потому что процесс завис.

ОБНОВЛЕНИЕ

Вот как я получаю данные:

queueSize--;
startPointer = (startPointer + 1) % mqueueSize;
data = (String) queue[startPointer];

вот как я добавляю данные

  queueSize++;
  endPointer = (endPointer + 1) % mqueueSize;
  queue[endPointer] = data;

public synchronized boolean isEmpty() {
        return queueSize== 0;
    }

    public synchronized boolean isFull() {
        return queueSize== mqueueSize;
    }

person kenny    schedule 15.12.2011    source источник


Ответы (5)


Почему у вас три утверждения synchronized? wait(0) снимает блокировку только с this, так что просто оставьте его и выгрузите synchronized из метода и synchronized(myLock).

Всякий раз, когда вы вызываете ожидание для какого-либо объекта (в данном случае вы вызываете this), блокировка этого объекта автоматически снимается, чтобы позволить другому потоку продолжить работу. Но вы никогда не вызываете wait на myLock (и не должны этого делать, потому что вы уже звоните на this). Эта часть является избыточной и вызывает взаимоблокировку.

Рассмотрим такой сценарий: поток, который должен добавить, блокирует myLock, но обнаруживает, что очередь заполнена, поэтому ждет. Это ожидание не освобождает блокировку myLock. Другой поток хочет получить данные, но не может войти в блок synchronized, поскольку первый поток не снял блокировку с myLock.

Вывод: удалите блоки synchronized(myLock).

person Tudor    schedule 15.12.2011
comment
как насчет синхронизированного (этого) в уведомлении, мне это все еще нужно, верно? - person kenny; 15.12.2011
comment
@kenny, да, иначе это вызовет исключение IllegalMonitorStateException. Вызовы ожидания и уведомления должны находиться внутри синхронизированного блока того же объекта. - person Tudor; 15.12.2011
comment
теперь поток работает нормально, пока мне не нужно выйти (нет больше данных), в моей логике я просто добавляю null в очередь, но кое-как он застревает в конце. - person kenny; 15.12.2011
comment
странно, но это происходит не постоянно... после тестирования я обнаружил, что если он застрял, он застрял в методе get. - person kenny; 15.12.2011
comment
@kenny, может быть, вам следует переместить уведомление после того места, где вы действительно что-то добавили. То же самое для получения. - person Tudor; 15.12.2011
comment
это может вызвать проблему: поток, который добавляет данные в очередь, имеет более одной ссылки на объект очереди (тот же объект, который существует в хэш-карте, только разные ссылки) - person kenny; 15.12.2011
comment
Я добавил синхронизированные методы, и все заработало! Зачем? - person kenny; 16.12.2011
comment
@kenny, потому что теперь весь ваш код последовательный, поскольку только один поток может войти в блокировку. Так что тупика практически нет, потому что нет параллелизма. :) - person Tudor; 16.12.2011
comment
я не понимаю, один поток не может добавлять данные, а другой не может читать данные одновременно? моя очередь представляет собой массив с двумя указателями (голова и хвост), которые я изменяю в соответствии с вызываемым методом, указатели, конечно, не могут быть изменены двумя потоками одновременно. - person kenny; 16.12.2011
comment
@kenny: если вы установите синхронизацию для обоих методов, они не могут одновременно вызываться двумя потоками. - person Tudor; 16.12.2011
comment
подождите, у меня 2 темы. первый только добавляет данные в очередь, а второй поток только получает данные из очереди (ни один поток не выполняет 2 операции, только одну). Мне нужно, чтобы оба потока работали параллельно, будет ли это работать с синхронизированными методами? - person kenny; 16.12.2011
comment
@kenny: Если у вас есть несколько методов с синхронизацией в одном и том же объекте, это эквивалентно размещению всего кода каждого метода внутри синхронизированного (этого). Таким образом, все потоки, входящие в любой из методов, должны получить блокировку этого. Следовательно, два потока, обращающиеся даже к разным методам, не могут выполнять их параллельно. - person Tudor; 16.12.2011
comment
Итак, как мне исправить проблему, с которой я столкнулся? Я добавил в свой пост больше кода, чтобы показать, как добавлять и читать данные в очередь. может проблема с isfull и isempty, потому что они тоже синхронизировались? Как насчет того факта, что я должен предотвратить одновременное изменение указателей двух потоков? - person kenny; 16.12.2011

Почему вы не заглянете в java. util.BlockingQueue. Возможно, это будет полезно в вашей ситуации.

В частности, обратите внимание на java.util.LinkedBlockingQueue, где, если вы укажете емкость очереди в конструкторе, очередь будет заблокирована.

person Valchev    schedule 15.12.2011

Удалите ключевое слово synchronized из ваших сигнатур методов, так как это означает, что вы держите монитор this для всего вызова метода — блоки synchronized(this) просто избыточны.

РЕДАКТИРОВАТЬ:

...Затем вызовите ожидание и уведомите myLock, а не this. И полностью забудьте о синхронизации на this. Это связано с тем, что во время ожидания (на this в вашем текущем коде) вы не снимаете блокировку myLock, поэтому другой поток не может добраться до notify().

person Costi Ciudatu    schedule 15.12.2011
comment
другой ответ говорит мне удалить синхронизированный (myLock), а вы говорите мне удалить синхронизированный (этот)... Я запутался... - person kenny; 15.12.2011
comment
удалить один из них :). Достаточно использовать один, это ключевой момент. И это также является обязательным в вашем случае для избавления от тупика. - person Costi Ciudatu; 15.12.2011
comment
Я не буду здесь спорить о том, какой из них лучше удалить, а просто заявлю, что я бы предпочел сохранить синхронизацию не на this, а на каком-то частном объекте монитора. - person Costi Ciudatu; 15.12.2011

Замените if на while. Не помешает перепроверить, действительно ли коллекция стала не пустой/не полной.

Два замка на самом деле не нужны. Одиночная блокировка будет работать почти так же хорошо и должна быть намного проще.

public synchronized T get()
{
    while(isEmpty())
        wait(0);

    notifyAll();

    return super.get();

}

public synchronized put(T t)
{

    while(isFull())
        wait(0);

    super.put(t);

    notifyAll();

}

Все потоки проснутся, когда что-то изменится. Но если они не могут выполнять свою работу, они будут wait в следующие notify.

person Piotr Praszmo    schedule 15.12.2011
comment
почему достаточно одного замка? если поток попытается добавить данные в полную очередь, в цикле он будет постоянно нажимать if (!isFull()). - person kenny; 15.12.2011
comment
@kenny Возможно, в этом нет необходимости, но в общем случае может быть несколько ожидающих потоков. - person Piotr Praszmo; 15.12.2011

Как уже упоминалось, в вашем коде слишком много аннотаций synchronized. Кроме того, условие включения wait проверяется в условном выражении if, но в идеале его следует проверять в цикле while, чтобы избежать ложные пробуждения. Вот набросок кода, который их исправляет.

// _isEmpty and _getData are private unsynchronized methods
public MyData get() throws InterruptedException {
  // wait and notify should be called from a block
  // synchronized on the same lock object (here myLock)       
  synchronized (myLock) {
    // the condition should be tested in a while loop
    // to avoid issues with spurious wakeups
    while (_isEmpty()) {
      // releases the lock and wait for a notify to be called
      myLock.wait();
    }
    // when control reaches here, we know for sure that
    // the queue is not empty
    MyData data = _getData();
    // try to wake up all waiting threads - maybe some thread
    // is waiting for the queue not to be full
    myLock.notifyAll();
  }
}

// _isFull and _putData are private unsynchronized methods
public void put(MyData obj) throws InterruptedException {
  synchronized (myLock) {
    while (_isFull()) {
      myLock.wait();
    }
    _putData(obj);
    myLock.notifyAll();
  }
}
person Binil Thomas    schedule 15.12.2011