Я пытаюсь создать синхронизатор 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)
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;
}
}
Просто примечание. У меня были некоторые проблемы с переходом 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()
Вам не нужно реализовывать это самостоятельно, вы можете использовать SynchronousQueue
Ссылки:
http://tutorials.jenkov.com/java-util-concurrent/synchronousqueue.html
Класс SynchronousQueue реализует интерфейс BlockingQueue. Прочтите текст BlockingQueue для получения дополнительной информации об интерфейсе.
SynchronousQueue - это очередь, которая может содержать только один внутренний элемент. Поток, вставляющий элемент в очередь, блокируется до тех пор, пока другой поток не возьмет этот элемент из очереди. Аналогичным образом, если поток пытается взять элемент, а в данный момент элемент отсутствует, этот поток блокируется до тех пор, пока поток не вставит элемент в очередь.
Похожие вопросы
Связанные вопросы
Новые вопросы
java
Java — это высокоуровневый объектно-ориентированный язык программирования. Используйте этот тег, если у вас возникли проблемы с использованием или пониманием самого языка. Этот тег часто используется вместе с другими тегами для библиотек и/или фреймворков, используемых разработчиками Java.