ожидание / уведомление против сна / прерывания против ReentrantLock.Condition - PullRequest
0 голосов
/ 28 сентября 2018

Я пишу механизм поиска по типу (android), который делает sqlite-запрос в фоновом потоке и отправляет результаты обратно в поток пользовательского интерфейса.В идеале поток должен ждать / спать, просыпаться, чтобы выполнить любой полученный объект Runnable, и возвращаться в режим сна.Каков наилучший способ добиться этого и почему?

В основном я хочу понять, каковы основные различия между этими 3 вариантами и какой из них лучше всего подходит для этого точного сценария

  1. сон / прерывание

    public class AsyncExecutorSleepInterrupt {
    private static Thread thread;
    private static Runnable runnable;
    
    static {
        thread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    if (runnable != null) {
                        runnable.run();
                        runnable = null;
                    }
                }
            }
        });
        thread.start();
    }
    
    public static void execute(Runnable runnable) {
        AsyncExecutorSleepInterrupt.runnable = runnable;
        thread.interrupt();
    }}
    
  2. ожидание / уведомление

    public class AsyncExecutorWaitNotify {
    private static Thread thread;
    private static Runnable runnable;
    
    private static final Object monitor = new Object();
    
    static {
        thread = new Thread(() -> {
            while (true) {
                synchronized (monitor) {
                    try {
                        monitor.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        continue;
                    }
                    if (runnable != null) {
                        runnable.run();
                        runnable = null;
                    }
                }
            }
        });
        thread.start();
    }
    
    public static void execute(Runnable runnable) {
        AsyncExecutorWaitNotify.runnable = runnable;
        synchronized (monitor) {
            monitor.notify();
        }
    }}
    
  3. ReentrantLock

    public class AsyncExecutorLockCondition {
    
    private static final ReentrantLock lock = new ReentrantLock();
    
    private static final Condition cond = lock.newCondition();
    
    private static Thread thread;
    
    private static Runnable runnable;
    
    static {
        thread = new Thread(() -> {
            while(true){
                try {
                    lock.lock();
                    cond.await();
                    runnable.run();
                    lock.unlock();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }
    
    public static void execute(Runnable runnable) {
        AsyncExecutorLockCondition.runnable = runnable;
        lock.lock();
        cond.signal();
        lock.unlock();
    }}
    

Ответы [ 2 ]

0 голосов
/ 28 сентября 2018

Лично мне не нравится первый подход, вероятно, из-за interrupt в основном.Что делать, если кто-то что-то вызывает и прерывает эту тему?Вы будете запускать произвольный код, вероятно, не лучшая идея.Кроме того, когда вы прерываете, вы фактически заполняете трассировку стека цепочками исключений, это самая дорогая часть из создаваемого исключения.

Но предположим, что вас не волнует второй пункт, и вы полностью контролируете первый;Вероятно, в этом подходе нет ничего плохого.

Теперь разница между Conditional и wait/notify в этом примере довольно мала.Я не знаю внутренних деталей и которые могут быть быстрее или лучше, но в целом Conditional предпочтительнее;главным образом по той причине, что ее легче читать, по крайней мере, для меня.Также Conditional может быть взято из другой блокировки все время, в отличие от synchronized.

Другие преимущества (не связанные здесь): вы можете создать несколько условий, таким образом пробуждая только нужную нить ;в отличие от notifyAll например.Тогда есть методы с истечением срока действия, такие как awaitUntil(Date) или await(long, TimeUnit) или awaitNanos.Есть даже метод, который будет await и вообще игнорировать interrupts: awaitUninterruptibly.

При этом вам не нужно lock::unlock после await, так как документация довольно яснав этом отношении:

Блокировка, связанная с этим условием, снимается атомарно ...

Гораздо более простой подход был бы:

static class AsyncExecutor {

    private static final ExecutorService service = Executors.newSingleThreadExecutor();

    public static void execute(Runnable runnable) {
        service.execute(runnable);
    }
}
0 голосов
/ 28 сентября 2018

Ничего из вышеперечисленного.Третий - самый близкий, но все же не лучший способ сделать это.Используйте обработчик looper + или очередь сообщений.Кроме того, должно быть сообщение, которое вы можете отправить любому потоку, чтобы сказать ему выйти из цикла и завершиться.В противном случае вы потеряете любую память, на которую он может ссылаться (что очень много, поскольку у него будет внутренняя ссылка на своего родителя), когда он больше не нужен, но все равно остается на вечность.Помните, что поток никогда не GCed, пока он не завершится.

...