Проблемы Java с уведомлением () / ожиданием ()

У меня есть генератор внутри экземпляра Iterator, который генерирует и возвращает один элемент каждый раз, когда next() вызывается на итераторе. Кажется, это работает, но я получаю возвращаемые значения null.

Код класса:

package spiral;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author student
 */
public class Spiral7DGenerator implements Iterator<List<Integer>> {
    private boolean releaseNext = false;
    private List<Integer> releaseList;

    public Spiral7DGenerator() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int t = 0; true; t++) {
                    loops(t);
                }
            }
        }).start();
    }

    @Override
    public boolean hasNext() {
        return true;
    }

    @Override
    public List<Integer> next() {
        synchronized(this) {
            releaseNext = true;
            notify();
            return releaseList;
        }
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    private void loops(int t) {
        for (int d1 = 0; d1 <= t; d1++) {
            for (int d2 = 0; d2 <= (t - d1); d2++) {
                for (int d3 = 0; d3 <= (t - d1 - d2); d3++) {
                    for (int d4 = 0; d4 <= (t - d1 - d2 - d3); d4++) {
                        for (int d5 = 0; d5 <= (t - d1 - d2 - d3 - d4); d5++) {
                            for (int d6 = 0; d6 <= (t - d1 - d2 - d3 - d4 - d5); d6++) {
                                int d7 = (t - d1 - d2 - d3 - d4 - d5 - d6);
                                generate(0, d1, d2, d3, d4, d5, d6, d7);
                            }
                        }
                    }
                }
            }
        }
    }

    private void generate(int pos, Integer... array) {
        if (pos == array.length) {
            List<Integer> list = new ArrayList<>();
            list.addAll(Arrays.asList(array));
            synchronized(this) {
                while (!releaseNext) {
                    try {
                        wait();
                    } catch (InterruptedException ex) {
                        Logger.getLogger(Spiral7DGenerator.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
            releaseNext = false;
            releaseList = list;
            return;
        }
        generate(pos + 1, array);
        array[pos] = -array[pos];
        if (array[pos] != 0) {
            generate(pos + 1, array);
        }
    }
}

Тестовый код:

package spiral;

import org.junit.Test;
import static org.junit.Assert.*;

/**
 *
 * @author Beheerder
 */
public class Spiral7DGeneratorTest {

    public Spiral7DGeneratorTest() {
    }

    @Test
    public void testHasNext() {
    }

    @Test
    public void testNext() {
        System.out.println("test");
        Spiral7DGenerator s7dg = new Spiral7DGenerator();
        System.out.println("s7dg.next() = " + s7dg.next());
        System.out.println("s7dg.next() = " + s7dg.next());
        System.out.println("s7dg.next() = " + s7dg.next());
        System.out.println("s7dg.next() = " + s7dg.next());
    }

    @Test
    public void testRemove() {
    }

}

Тестовый вывод:

test
s7dg.next() = null
s7dg.next() = null
s7dg.next() = null
s7dg.next() = null

Боюсь, это простая вещь, но я совершенно не замечаю ее.


person skiwi    schedule 12.08.2013    source источник
comment
Вам действительно нужно писать такой сложный код. Это совершенно нечитаемо и трудно для меня, по крайней мере, понять. У меня заканчивается память при чтении этих вложенных циклов.   -  person Juned Ahsan    schedule 12.08.2013
comment
@JunedAhsan Хотя сам вопрос не об этом коде, я могу объяснить, почему он такой сложный. Это потому, что делать спирали в целом сложно, конечно, я бы хотел сделать их универсальными, но в настоящее время это слишком сложно для меня.   -  person skiwi    schedule 12.08.2013
comment
Ваш цикл в Spiral7DGenerator() const никогда не заканчивается и запускает новый поток на каждой итерации. Это ошибка (поскольку память ограничена)   -  person harrybvp    schedule 12.08.2013
comment
Использование нескольких потоков редко помогает упростить код. Я все еще думаю, что это намного проще и с большей вероятностью будет работать " title="проблема с преобразованием фиксированного количества циклов for в параметризованное количество"> stackoverflow.com/questions/18074982/   -  person Peter Lawrey    schedule 12.08.2013
comment
Другой ответ stackoverflow.com/questions/11570132/   -  person lkreinitz    schedule 22.07.2016


Ответы (2)


Идея здесь в том, что рабочий поток передает поток результатов в основной поток, верно?

Я предлагаю вам не пытаться создавать логику управления параллелизмом вручную, а вместо этого использовать SynchronousQueue. Код будет выглядеть так:

public class Spiral7DGenerator implements Iterator<List<Integer>> {
    private BlockingQueue<List<Integer>> spirals = new SynchronousQueue<List<Integer>>();

    @Override
    public List<Integer> next() {
        return spirals.take();
    }

    private void generate(int pos, Integer... array) {
        if (pos == array.length) {
            List<Integer> list = new ArrayList<>();
            list.addAll(Arrays.asList(array));
            spirals.put(list);
            return;
        }
    // etc
    }
}
person Tom Anderson    schedule 12.08.2013
comment
Спасибо! Это именно то, что мне нужно. Очень жаль, что у Java нет yield эквивалента Python. - person skiwi; 12.08.2013
comment
Это определенно. В Scala, конечно же, добавлены продолжения, позволяющие создавать такие вещи, как генераторы. Я думаю, что это вряд ли когда-либо придет к Java. - person Tom Anderson; 12.08.2013
comment
На самом деле, это может быть не на 100% то, что я хочу. Я хочу, чтобы generate() делал что-то только тогда, когда spirals.isEmpty(). Можно ли (легко) изменить BlockingQueue таким образом, чтобы он блокировался при spirals.size() > threshold? Поскольку я хотел бы, чтобы он блокировал/ожидал всякий раз, когда spirals.size() > 0. Затем подождите, пока не будет вызван next(), а затем снова сгенерируйте следующий элемент и т. д. - person skiwi; 12.08.2013
comment
Как бы то ни было, spirals.put будет блокироваться до тех пор, пока не появится основной поток и не вызовет spirals.take, поэтому в каждый момент времени через очередь будет проходить только одно значение. Если вы хотите иметь большее, но все же фиксированное ограничение на размер очереди, замените SynchronousQueue на ArrayBlockingQueue любой необходимой вам емкости. - person Tom Anderson; 12.08.2013
comment
Ах, хорошо, так что SynchronousQueue действительно то, что мне нужно, я должен был прочитать документацию, прежде чем спрашивать. - person skiwi; 12.08.2013
comment
Документация JDK, как правило, очень хороша, а классы параллелизма превосходны. Я согласен, что это стоит прочитать :). - person Tom Anderson; 12.08.2013

Я не совсем понял ваш код, но могу сделать два замечания:

  • releaseList должен быть объявлен volatile (по крайней мере), так как он назначается вне синхронизированного блока.
  • ваш метод next не ждет, пока уведомленный поток проснется и выдаст результат. Это состояние гонки. Вам нужно next, чтобы дождаться дополнительного мьютекса (или чего-то еще), чтобы позволить генератору уведомить, что releaseList был установлен. (На самом деле, когда вы это сделаете, вам больше не нужно будет делать releaseList volatile.)
person Adrian Pronk    schedule 12.08.2013
comment
Я подозреваю, что тот факт, что next() вообще не ждет рабочего потока, приводит ко всем нулям. Вы абсолютно правы в том, что releaseListreleaseNext) тоже не обновляются безопасно. - person Tom Anderson; 12.08.2013