Я пытаюсь создать синхронизатор SingleBlockingQueue<T>, который позволяет одному потоку offer() использовать его элемент, а другому потоку take() его. Только один элемент T удерживается внутри SingleBlockingQueue<T> за раз, и проталкивающий поток блокируется на offer(), если предыдущий элемент ожидает, пока принимающий поток take() его . Проталкивающий поток будет продолжать подталкивать элементы до тех пор, пока не вызовет setComplete(), а принимающий поток будет продолжать вызывать take(), пока isComplete() ложно. Принимающий поток заблокируется, если он ожидает элемента.

Вот синхронизатор, который у меня пока есть.

import java.util.concurrent.atomic.AtomicBoolean;

public final class SingleBlockingQueue<T> {

    private volatile T value;
    private final AtomicBoolean isComplete = new AtomicBoolean(false);
    private final AtomicBoolean isPresent =  new AtomicBoolean(false);

    public void offer(T value) throws InterruptedException {
        while (isPresent.get()) {
            this.wait();
        }
        this.value = value;
        synchronized(this) {
            this.notifyAll();
        }
    }
    public boolean isComplete() {
        return !isPresent.get() && isComplete.get();
    }
    public void setComplete() {
        isComplete.set(true);
    }
    public T take() throws InterruptedException {
        while (!isPresent.get()) {
            this.wait();
        }
        T returnValue = value;
        isPresent.set(false);
        synchronized(this) {
            this.notifyAll();
        }
        return returnValue;
    }
}

Вот пример использования в Котлине

    val queue = SingleBlockingQueue<Int>()

    thread {
        for (i in 1..1000) {
            queue.offer(i)
        }
        queue.setComplete()
    }

    thread {
        while (!queue.isComplete) {
            println(queue.take())
        }
    }

    Thread.sleep(100000)

Однако я получаю сообщение об ошибке, и на данный момент я немного не в себе. Я давно не делал синхронизаторы благодаря RxJava. Что именно я делаю не так?

Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at com.swa.rm.common.util.SingleBlockingQueue.take(SingleBlockingQueue.java:29)
    at RxOperatorTest$testSingleBlockingQueue$2.invoke(RxOperatorTest.kt:33)
    at RxOperatorTest$testSingleBlockingQueue$2.invoke(RxOperatorTest.kt:8)
    at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:18)
0
tmn 25 Май 2016 в 23:15

3 ответа

Лучший ответ

Как указывали другие, вы можете использовать существующую реализацию в SynchronousQueue.

Если вы хотите реализовать свой собственный, вы довольно близки, вам просто нужно убедиться, что вызовы wait() находятся внутри блока synchronized.

К сожалению, я считаю, что механизм isComplete() / setComplete() в вашем исходном коде находится в состоянии гонки, поскольку setComplete() может быть вызван после того, как isComplete() вернет false и до или даже во время выполнения потоком чтения take(). Это потенциально может повредить цепочку чтения.

  public final class SingleBlockingQueue<T> {
    private final Object lock = new Object();
    private T value;
    private boolean present = false;

    public void offer(T value) throws InterruptedException {
      synchronized (lock) {
        while (present)
          lock.wait();
        this.value = value;
        present = true;
        lock.notifyAll();
      }
    }

    public T take() throws InterruptedException {
      synchronized (lock) {
        while (!present)
          lock.wait();
        T returnValue = value;
        value = null; // Should release reference
        present = false;
        lock.notifyAll();
        return returnValue;
      }
    }
  }

Для сравнения, может быть более естественным реализовать этот вид очереди на основе объектов Semaphore или Condition. Вот реализация, использующая пару семафоров для сигнализации пустого / полного состояния.

  public final class SingleBlockingQueue<T> {
    private volatile T value;
    private final Semaphore full = new Semaphore(0);
    private final Semaphore empty = new Semaphore(1);

    public void offer(T value) throws InterruptedException {
      empty.acquire();
      this.value = value;
      full.release();
    }

    public T take() throws InterruptedException {
      full.acquire();
      T returnValue = value;
      value = null; // Should release reference
      empty.release();
      return returnValue;
    }
  }
1
clstrfsck 26 Май 2016 в 12:31

Просто примечание. У меня были некоторые проблемы с переходом ResultSet вперед из-за времени вызовов next() в структуре RxJava-JDBC. Я пошел с этой реализацией, изменив ранее данные ответы.

public final class SingleBlockingQueue<T> {
    private volatile T value;
    private final Semaphore nextGate = new Semaphore(0);
    private final Semaphore waitGate = new Semaphore(0);

    private volatile boolean hasValue = true;
    private volatile boolean isFirst = true;

    public void offer(T value) throws InterruptedException {
        if (isFirst) {
            nextGate.acquire();
            isFirst = false;
        }
        this.value = value;
        waitGate.release();
        nextGate.acquire();
    }

    public T take() throws InterruptedException {
        T returnValue = value;
        value = null; // Should release reference
        return returnValue;
    }
    public boolean next() throws InterruptedException {
        nextGate.release();
        waitGate.acquire();
        return hasValue;
    }
    public void setDone() {
        hasValue = false;
        waitGate.release();
    }
}

Это то, для чего я его использовал: превращение RxJava Observable<T> в Sequence<T> в Kotlin.

import com.github.davidmoten.rx.jdbc.QuerySelect
import rx.Observable
import rx.Scheduler
import rx.lang.kotlin.subscribeWith
import java.io.Closeable

class ObservableIterator<T>(
        observable: Observable<T>
) : Iterator<T>, Closeable {

    private val queue = SingleBlockingQueue<T>()

    private val subscription =
            observable
                    .subscribeWith {
                        onNext { queue.offer(it) }
                        onCompleted { queue.setDone() }
                        onError { queue.setDone() }
                    }

    override fun hasNext(): Boolean {
        return queue.next()
    }

    override fun next(): T {
        return queue.take()
    }
    override fun close() {
        subscription.unsubscribe()
        queue.setDone()
    }
}

fun <T> Observable<T>.asSequence() = ObservableIterator(this).asSequence()

fun QuerySelect.Builder.asSequence(scheduler: Scheduler) = get { it }
        .subscribeOn(scheduler)
        .asSequence()
0
tmn 26 Май 2016 в 19:56

Вам не нужно реализовывать это самостоятельно, вы можете использовать SynchronousQueue

Ссылки:

SynchronousQueue javadoc

http://tutorials.jenkov.com/java-util-concurrent/synchronousqueue.html

Класс SynchronousQueue реализует интерфейс BlockingQueue. Прочтите текст BlockingQueue для получения дополнительной информации об интерфейсе.

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

2
Elia Rohana 26 Май 2016 в 07:07