Очередь потоков пула с одним ресурсом Grails / Java - PullRequest
1 голос
/ 22 января 2012

Я пытаюсь настроить сервер, который предоставляет истинно-случайное число с устройства, Quantis - Quantum Random Number Generator. Я создал веб-приложение с платформой Grails, работающей на сервере Tomcat на Ubuntu.

Так как есть только 1 устройство, я должен запланировать потоки, которые обращаются к нему, правильно? Поэтому я установил семафоры (с 1 ресурсом) на функции (ReadInt, ReadDouble, ReadFloat), которые вызывают это устройство. Объект, который содержит эти функции, называется Quantis, хранящимся в пакетах исходного кода Java для приложения Grails, он реализован как одноэлементный; Затем контроллеры будут вызывать и экземпляр этого объекта и его функций. Затем каждая из этих функций будет вызывать библиотеку Quantis в системе для чтения потока с устройства <- теперь это критическая зона. Мне нужно убедиться, что есть только один запрос к этому устройству за один раз. </p>

Семафоры работают нормально. Но если я обновлю страницу (получая поток случайных чисел) очень быстро (например, +/- 10 раз), она вылетит. Я "вслепую" перепробовал множество подходов из интернета, в том числе grails executors, но, похоже, ничего не работает (хотя, возможно, я действительно не реализовал их правильно).

Кто-нибудь знает, как мне это решить?

Вот мой код для одной из функций: (все они выглядят примерно в одном стиле, но при получении данных вызывают разные функции системной библиотеки)

    private static final Semaphore ticket = new Semaphore(1, true);
    ...
    public int ReadInt(int min, int max) throws QuantisException {
        while (true) {
            try {
                ticket.acquire();
                int data = QuantisReadScaledInt(deviceType.getType(), deviceNumber, min, max);
                ticket.release();
                return data;
            } catch (InterruptedException ex) {
            } catch (QuantisException ex) {
                ticket.release();
                throw ex;
            }
        }
    }

1 Ответ

1 голос
/ 22 января 2012

Во-первых, « он будет падать » - плохое описание того, что происходит. Любое исключение? Что именно происходит?

Во-вторых, вы уверены, что вам нужно синхронизировать доступ к API. Если он предоставляет Java API, есть большая вероятность, что этот API уже синхронизирован и ваш семафор не нужен.

В-третьих, если вы приобрели семафор, вы должны выпустить его в блоке finally. Это гарантирует, что он выпущен, что бы ни происходило внутри блока try:

ticket.acquire();
try {
    ...
}
catch (...) 
finally {
    ticket.release();
}

Четвертый: я не понимаю смысла цикла while(true). Единственный раз, когда он зацикливается, возникает InterruptedException. И InterruptedException точно используется, чтобы сигнализировать потоку, что он должен прекратить выполнение как можно скорее. Таким образом, ваш метод должен генерировать это исключение вместо того, чтобы его проглотить.

Наконец, вы должны изучить соглашения об именах Java и придерживаться их. Методы начинаются со строчной буквы.

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

public int readInt(int min, int max) throws QuantisException, InterruptedException {
    ticket.acquire();
    try {
        return quantisReadScaledInt(deviceType.getType(), deviceNumber, min, max);
    }
    finally {
        ticket.release();
    }
}

Если вы хотите убедиться, что только один поток имеет доступ к собственным функциям библиотеки, используйте такой класс:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class SingleThreadAccess {
    public static final SingleThreadAccess INSTANCE = new SingleThreadAccess();

    private ExecutorService executor;

    // to be called by ServletContextListener.contextInitialized()
    public void init() {
        executor = Executors.newSingleThreadExecutor();
    }

    // to be called by ServletContextListener.contextDestroyed()
    public void shutdown() {
        executor.shutdown();
        try {
            executor.awaitTermination(2L,TimeUnit.SECONDS);
        }
        catch (InterruptedException e) {
        }
        executor.shutdownNow();
    }

    public int readInt(int min, int max) throws QuantisException, InterruptedException {
        Callable<Integer> task = new Callable<Integer>() {
            @Override
            public Integer call() throws QuantisException {
                return quantisReadScaledInt(deviceType.getType(), deviceNumber, min, max);
            }
        };
        Future<Integer> future = executor.submit(task);
        try {
            future.get();
        }
        catch (ExecutionException e) {
            unwrap(e);
        }
    }

    private void unwrap(ExecutionException e) throws QuantisException {
        Throwable t = e.getCause();
        if (t instanceof QuantisException) {
            throw (QuantisException) t;
        }
        throw new RuntimeException(e);
    }
}
...