Java 等待/通知vs睡眠/中断vs重入锁定状态

Java 等待/通知vs睡眠/中断vs重入锁定状态,java,android,multithreading,thread-sleep,reentrantlock,Java,Android,Multithreading,Thread Sleep,Reentrantlock,我正在编写一个随键入搜索机制(android),它在后台线程中进行sqlite查询,并将结果发布回UI线程。理想情况下,线程应该等待/睡眠,醒来执行任何接收到的可运行对象,然后返回睡眠状态。实现这一目标的最佳方式是什么?为什么 基本上,我想了解这3个选项之间的关键区别是什么,以及哪一个最适合这个具体场景 睡眠/中断 public class AsyncExecutorSleepInterrupt { private static Thread thread; private static Run

我正在编写一个随键入搜索机制(android),它在后台线程中进行sqlite查询,并将结果发布回UI线程。理想情况下,线程应该等待/睡眠,醒来执行任何接收到的可运行对象,然后返回睡眠状态。实现这一目标的最佳方式是什么?为什么

基本上,我想了解这3个选项之间的关键区别是什么,以及哪一个最适合这个具体场景

  • 睡眠/中断

    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();
    }}
    
  • 等待/通知

    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();
        }
    }}
    
  • 重入锁

    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();
    }}
    

  • 以上都没有。第三种方法是最接近的,但仍然不是最好的方法。使用looper+处理程序或消息队列。另外,应该有一条消息可以发送给任何线程,告诉它退出循环并终止。否则,当不再需要它时,你将泄漏它可以引用的任何内存(这是一个很大的问题,因为它将有一个对其父级的内部引用),但无论如何,它将永远存在。请记住,线程在退出之前是不会被GCed的。

    就我个人而言,我有点不喜欢第一种方法,可能主要是因为
    中断。如果有人,有人,有人,有人,有人,有人,有人,有人,有人,有人,有人,有人,有人,有人,有人,有人,有人,有人,有人,有人,有人,有人?您可能会运行一些任意代码,这可能不是最好的主意。此外,当您中断时,您实际上是在用异常链填充堆栈跟踪,这是抛出异常最昂贵的部分

    但是假设你不关心第二点,你完全控制了第一点;这种方法可能没有什么问题

    现在,在本例中,
    Conditional
    wait/notify
    之间的差异非常小。我不知道内部细节,也不知道哪种可能更快或更好,但一般来说,
    Conditional
    是首选;主要是因为它更容易阅读,至少对我来说是这样。另外,与
    同步
    不同的是,
    条件
    可以始终从不同的锁中获取

    其他优势(这里不相关):您可以创建多个条件,从而只唤醒您想要的线程;例如,与
    notifyAll
    不同。还有一些过期的方法,比如
    waituntil(Date)
    wait(long,TimeUnit)
    waitnanos
    。甚至有一种方法可以
    等待
    并忽略
    中断
    不间断地等待

    也就是说,您不需要在
    wait
    之后使用
    lock::unlock
    ,因为文档在这方面非常清楚:

    与此条件关联的锁已自动释放

    更直接的方法是:

    static class AsyncExecutor {
    
        private static final ExecutorService service = Executors.newSingleThreadExecutor();
    
        public static void execute(Runnable runnable) {
            service.execute(runnable);
        }
    }
    

    我认为向只执行此操作的线程添加循环器是一种资源浪费,最多浪费100个字节。活套不是重量级物体。我对活套很熟悉,但有一点不清楚。它是无限循环还是休眠/等待下一条消息?它休眠。基本上,它会在一个同步的堆栈上等待一条消息。如果您想要最简单的实现,HandlerThread基本上会为您设置所有这些。Re,“理想情况下,线程应该等待/睡眠,唤醒以执行任何接收到的可运行对象,然后返回睡眠。”您所描述的正是
    ThreadPoolExecutor
    中的工作线程所做的。@通过这种方式,两个连续调用将在两个不同的线程上执行,不是吗?我不知道“这种方式”是什么意思。我想说的是,您的三个示例看起来都像是在尝试重新创建
    java.util.concurrent.ThreadPoolExecutor
    类已经为您做的事情。;如果将
    ThreadPoolExecutor
    配置为只有一个工作线程,则可以保证提交的任务将按照提交的顺序逐个执行。静态函数java.util.concurrent.Executors.newSingleThreadExecutor()
    就是为了这个目的而存在的。