Уведомлять Темы, Когда Счетчик Изменения - PullRequest
6 голосов
/ 07 марта 2012

Я пытаюсь спроектировать класс как Code Kata для себя, у которого есть свойство value, которое можно установить, и класс может выдавать экземпляры ValueListener. Идея состоит в том, что существует один экземпляр ValueHolder со множеством клиентских потоков, обращающихся к нему одновременно. Каждый клиентский поток запросил ValueWatcher и вызвал waitForValue ().

То, с чем я действительно борюсь, это то, какое условие я должен использовать в цикле while вокруг wait (), чтобы избежать ложных уведомлений (т.е. значение не изменилось). Я вижу, что этот дизайн может позволить экземплярам ValueWatcher пропускать обновления, но на данном этапе это меньше беспокоит.

Буду признателен за любые рекомендации по предложению!

public class ValueHolder {

  private int value = 0;
  private final Object monitor = new Object();

  public void setValue(int value) {
    synchronized(monitor) {
      this.value = value;
      monitor.notifyAll();
    }
  }

  ValueWatcher createChangeWatcher() {
    return new ValueWatcher();
  }

  private class ValueWatcher {
    public int waitForValue() {
      synchronized(monitor) {
        while (==== ??? =====) {
          monitor.wait();
          return value;
        }
      }
    }
  }     
}

Ответы [ 5 ]

3 голосов
/ 07 марта 2012

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

Edit: Первоначально у меня был AtomicLong, но я краду идею обертки от @John Vint.

private final VersionValue versionValue = new VersionValue();

public void setValue(int value) {
    synchronized (monitor) {
       versionValue.value = value;
       versionValue.version++;
       monitor.notifyAll();
    }
}

 private class ValueWatcher {
     private long localVersion = 0;
     public int waitForValue() {
         synchronized (monitor) {
             while (true) {
                 if (localVersion < versionValue.version) {
                     // NOTE: the value might have been set twice here
                     localVersion = versionValue.version;
                     return versionValue.value;
                 }
                 monitor.wait();
             }
         }
     }
}

private static class VersionValue {
    int value;
    long version;
}

Кроме того, хотя возможны ложные пробуждения, важно помнить, что текст:

Всегда вызывайте wait внутри цикла, который проверяет ожидаемое условие. Не думайте, что прерывание было для конкретного условия, которое вы ожидали, или что условие все еще выполняется.

Больше о гоночных условиях и моделях производитель / потребитель, чем о ложных пробуждениях. См. мою страницу здесь об этом .

2 голосов
/ 07 марта 2012

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

    private final StampedValue stamped = new StampedValue();

    public void setValue(int value) {
        synchronized (monitor) {
            this.stamped.value = value;
            this.stamped.lastUpdated = System.currentTimeMillis();
            monitor.notifyAll();
        }
    }
    private class ValueWatcher {

        public int waitForValue() { 
          synchronized(monitor) {
               long enteredOn = System.currentTimeMillis();    
               while (enteredOn > stamped.lastUpdated) {
                   monitor.wait();
               }
               return stamped.value;
          }
        }
    }
    private class StampedValue {
        long lastUpdated = System.currentTimeMillis();
        int value;
    }
1 голос
/ 07 марта 2012

Как насчет каждого слушателя, имеющего BlockingQueue, который он дает потоку установки значений как часть своей регистрации в качестве слушателя?Затем, когда значение изменяется, поток установки значений просто зацикливается на каждой из этих очередей, присваивая ему новое значение.Возможно, вы захотите использовать BlockingQueue.offer в этом цикле, так что если один поток еще не готов получить новое значение, он не помешает другим потокам получить его.

Это может быть ненаиболее эффективный подход, но он прост, и параллельная структура (т. е. сложная часть) хорошо проверена и поддерживается для вас.И это не так уж и неэффективно.

0 голосов
/ 07 марта 2012
public class ValueHolder {

    private final Object monitor = new Object();
    private LinkedList<WeakReference<ValueWatcher>> waiters = new LinkedList<WeakReference<ValueWatcher>>();

    public void setValue(int value) {
        synchronized (monitor) {
            Iterator<WeakReference<ValueWatcher>> it = waiters.iterator();
            while (it.hasNext()) {
                WeakReference<ValueWatcher> ref = it.next();
                if (ref.get() == null)
                    it.remove();
                else
                    ref.get().waitingList.add(value);
            }
            monitor.notifyAll();
        }
    }

    ValueWatcher createChangeWatcher() {
        ValueWatcher ret = new ValueWatcher();
        synchronized( monitor ) {
            waiters.add(new WeakReference<ValueWatcher>(ret));
        }
        return ret;
    }

    private class ValueWatcher {

        private Queue<Integer> waitingList = new LinkedList<Integer>();

        public int waitForValue() {
            synchronized (monitor) {
                while (waitingList.isEmpty()) {
                    monitor.wait();
                }
                return waitingList.poll();
            }
        }
    }
}

Идея состоит в том, что вы отслеживаете, кто ожидает, и у каждого наблюдателя есть очередь значений, которые были установлены с момента последнего вызова waitForValue(). Это избавляет от необходимости хранить value в ValueHolder, что хорошо, поскольку ко времени пробуждения ValueWatcher это могло бы измениться несколько раз. Недостаток этого метода заключается в том, что, как вы можете видеть, создание новых наблюдателей будет блокироваться до тех пор, пока монитор не освободится.

0 голосов
/ 07 марта 2012
private class ValueWatcher {
  private int oldValue = 0;

  public int waitForValue() {
    synchronized(monitor) {
      while (value == oldValue) {
        monitor.wait();
      }
      oldValue = value
      return oldValue;
    }
  }
}  
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...