2

I'm writing a search-as-you-type mechanism (android) that makes an sqlite query in a background thread and posts the results back to the UI thread. Ideally, the thread should wait/sleep, wake up to execute any received Runnable object and go back to sleep. what's the best way to achieve this and why?

Basically I want to understand what are the key differences between these 3 options and which one is best for this exact scenario

  1. sleep/interrupt

    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. wait/notify

    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();
    }}
    
Nisse Engström
  • 4,738
  • 23
  • 27
  • 42
Nick
  • 585
  • 4
  • 11
  • Re, "Ideally, the thread should wait/sleep, wake up to execute any received Runnable object and go back to sleep." What you described there is exactly what the worker threads in a `ThreadPoolExecutor` do. – Ohm's Lawman Sep 28 '18 at 13:14
  • @besmirched this way two sequential calls will get executed on two different threads, no? – Nick Sep 28 '18 at 13:24
  • I don't know what "this way" means. All I'm saying is, all three of your examples look like attempts to re-create what the `java.util.concurrent.ThreadPoolExecutor` class already does for you. – Ohm's Lawman Sep 28 '18 at 13:33
  • P.S.; If you configure a `ThreadPoolExecutor` to have just one worker thread, that will guarantee that the tasks you submit will be executed one-by-one in the order that you submitted them. The static function, `java.util.concurrent.Executors.newSingleThreadExecutor()` exists for exactly that purpose. – Ohm's Lawman Sep 28 '18 at 14:01

2 Answers2

2

Personally, I sort of dislike the first approach, probably because of interrupt mainly. What if someone, something calls and interrupts that thread somehow? You would be running some arbitrary code, probably not the best idea. Also, when you interrupt, you are actually filling the stack trace with exception chains, this is the most expensive part from an Exception being thrown.

But supposing you don't care about the second point, and you are totally in control of the first; there's probably nothing wrong with this approach IMO.

Now the difference between Conditional and wait/notify in this example is rather small. I don't know the internal details and which might be faster or better, but in general Conditional is preferred; mainly for the reason that it's easier to read, at least to me. Also a Conditional can be taken from a different lock all the time, unlike synchronized.

Other advantages are (unrelated here): you can create multiple Conditions, thus waking up only the thread you want; unlike notifyAll for example. Then there are methods with expiration, like awaitUntil(Date) or await(long, TimeUnit) or awaitNanos. There is even a method that will await and ignore interrupts , at all : awaitUninterruptibly.

That being said you don't need lock::unlock after await as the documentation is pretty clear in this regard:

The lock associated with this Condition is atomically released ...

An a lot more straightforward approach would be:

static class AsyncExecutor {

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

    public static void execute(Runnable runnable) {
        service.execute(runnable);
    }
}
Eugene
  • 117,005
  • 15
  • 201
  • 306
1

None of the above. The third is closest, but still not the best way to do it. Use a looper+handler, or a message queue. Also, there should be a message you can send to any thread to tell it to exit its loop and terminate. Otherwise you'll leak any memory it can reference (which is a lot since it will have an internal reference to its parent) when its no longer needed but sticks around for eternity anyway. Remember a thread is never GCed until it exits.

Gabe Sechan
  • 90,003
  • 9
  • 87
  • 127