【问题标题】:wait/notify vs sleep/interrupt vs ReentrantLock.Condition等待/通知 vs 睡眠/中断 vs ReentrantLock.Condition
【发布时间】:2018-09-28 12:56:28
【问题描述】:

我正在编写一个按你输入的搜索机制 (android),它在后台线程中进行 sqlite 查询并将结果发布回 UI 线程。理想情况下,线程应该等待/睡眠,唤醒以执行任何接收到的 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. 可重入锁

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

【问题讨论】:

  • Re,“理想情况下,线程应该等待/睡眠,唤醒以执行任何接收到的 Runnable 对象并重新进入睡眠状态。”你所描述的正是ThreadPoolExecutor 中的工作线程所做的。
  • @besmirched 这样两个顺序调用将在两个不同的线程上执行,不是吗?
  • 我不知道“这样”是什么意思。我要说的是,您的所有三个示例看起来都在尝试重新创建 java.util.concurrent.ThreadPoolExecutor 类已经为您所做的事情。
  • P.S.;如果您将ThreadPoolExecutor 配置为只有一个工作线程,这将保证您提交的任务将按照您提交的顺序一个接一个地执行。静态函数 java.util.concurrent.Executors.newSingleThreadExecutor() 正是为此目的而存在的。

标签: java android multithreading thread-sleep reentrantlock


【解决方案1】:

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

但是假设您不关心第二点,而您完全控制了第一点;这种方法 IMO 可能没有任何问题。

现在这个例子中Conditionalwait/notify 之间的区别很小。我不知道内部细节,哪个可能更快或更好,但总的来说Conditional 是首选;主要是因为它更容易阅读,至少对我来说是这样。与synchronized 不同,Conditional 也可以一直从不同的 锁中获取。

其他优点是(这里不相关):可以创建多个条件,从而只唤醒你想要的线程;不像notifyAll 例如。然后是有过期的方法,比如awaitUntil(Date)await(long, TimeUnit)awaitNanos。甚至还有一种方法可以 await 并完全忽略 interruptsawaitUninterruptibly

话虽如此,在await 之后您不需要lock::unlock,因为文档在这方面非常清楚:

与此 Condition 关联的锁被原子释放 ...

更直接的方法是:

static class AsyncExecutor {

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

    public static void execute(Runnable runnable) {
        service.execute(runnable);
    }
}

【讨论】:

    【解决方案2】:

    以上都不是。第三个是最接近的,但仍然不是最好的方法。使用 looper+handler 或消息队列。此外,应该有一条消息可以发送给任何线程,告诉它退出循环并终止。否则,当不再需要它但会永远存在时,您将泄漏它可以引用的任何内存(这是很多,因为它将对其父级进行内部引用)。请记住,线程在退出之前永远不会被 GC。

    【讨论】:

    • 我相信在只做这个的线程中添加一个looper是浪费资源
    • 你最多浪费了一百个字节。 Looper 不是重量级的对象。
    • 我很熟悉弯针是什么,但有一点不清楚。它是无限循环还是休眠/等待下一条消息?
    • 它在睡觉。基本上它会在同步堆栈上等待将消息放入其中。
    • 如果你想要最简单的实现,HandlerThread 基本上都会为你设置好。
    猜你喜欢
    • 2017-07-02
    • 2014-07-17
    • 2013-05-29
    • 2012-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-24
    • 2020-02-11
    相关资源
    最近更新 更多